/*!
    \file generator_java.cpp
    \brief Fast binary encoding Java generator implementation
    \author Ivan Shynkarenka
    \date 20.04.2018
    \copyright MIT License
*/

#include "generator_java.h"

namespace FBE {

void GeneratorJava::Generate(const std::shared_ptr<Package>& package)
{
    std::string domain = (package->domain && !package->domain->empty()) ? (*package->domain + ".") : "";

    GenerateFBEPackage(domain, "fbe");
    GenerateFBEPair(domain, "fbe");
    GenerateFBEUUIDGenerator(domain, "fbe");
    GenerateFBEBuffer(domain, "fbe");
    GenerateFBEModel(domain, "fbe");
    GenerateFBEFieldModel(domain, "fbe");
    GenerateFBEFieldModel(domain, "fbe", "Boolean", "boolean", "", "1", "false");
    GenerateFBEFieldModel(domain, "fbe", "Byte", "byte", "", "1", "(byte)0");
    GenerateFBEFieldModel(domain, "fbe", "Char", "char", "(byte)", "1", "'\\u0000'");
    GenerateFBEFieldModel(domain, "fbe", "WChar", "char", "(int)", "4", "'\\u0000'");
    GenerateFBEFieldModel(domain, "fbe", "Int8", "byte", "", "1", "(byte)0");
    GenerateFBEFieldModel(domain, "fbe", "Int16", "short", "", "2", "(short)0");
    GenerateFBEFieldModel(domain, "fbe", "Int32", "int", "", "4", "0");
    GenerateFBEFieldModel(domain, "fbe", "Int64", "long", "", "8", "0l");
    GenerateFBEFieldModel(domain, "fbe", "Float", "float", "", "4", "0.0f");
    GenerateFBEFieldModel(domain, "fbe", "Double", "double", "", "8", "0.0d");
    GenerateFBEFieldModel(domain, "fbe", "UUID", "java.util.UUID", "", "16", "UUIDGenerator.nil()");
    GenerateFBEFieldModelDecimal(domain, "fbe");
    GenerateFBEFieldModelDate(domain, "fbe");
    GenerateFBEFieldModelTimestamp(domain, "fbe");
    GenerateFBEFieldModelBytes(domain, "fbe");
    GenerateFBEFieldModelString(domain, "fbe");
    if (Final())
    {
        GenerateFBESize(domain, "fbe");
        GenerateFBEFinalModel(domain, "fbe");
        GenerateFBEFinalModel(domain, "fbe", "Boolean", "boolean", "", "1", "false");
        GenerateFBEFinalModel(domain, "fbe", "Byte", "byte", "", "1", "(byte)0");
        GenerateFBEFinalModel(domain, "fbe", "Char", "char", "(byte)", "1", "'\\u0000'");
        GenerateFBEFinalModel(domain, "fbe", "WChar", "char", "(int)", "4", "'\\u0000'");
        GenerateFBEFinalModel(domain, "fbe", "Int8", "byte", "", "1", "(byte)0");
        GenerateFBEFinalModel(domain, "fbe", "Int16", "short", "", "2", "(short)0");
        GenerateFBEFinalModel(domain, "fbe", "Int32", "int", "", "4", "0");
        GenerateFBEFinalModel(domain, "fbe", "Int64", "long", "", "8", "0l");
        GenerateFBEFinalModel(domain, "fbe", "Float", "float", "", "4", "0.0f");
        GenerateFBEFinalModel(domain, "fbe", "Double", "double", "", "8", "0.0d");
        GenerateFBEFinalModel(domain, "fbe", "UUID", "java.util.UUID", "", "16", "UUIDGenerator.nil()");
        GenerateFBEFinalModelDecimal(domain, "fbe");
        GenerateFBEFinalModelDate(domain, "fbe");
        GenerateFBEFinalModelTimestamp(domain, "fbe");
        GenerateFBEFinalModelBytes(domain, "fbe");
        GenerateFBEFinalModelString(domain, "fbe");
    }
    if (Proto())
    {
        GenerateFBESender(domain, "fbe");
        GenerateFBEReceiver(domain, "fbe");
    }
    if (JSON())
        GenerateFBEJson(domain, "fbe");

    GeneratePackage(package);
}

void GeneratorJava::GenerateHeader(const std::string& source)
{
    std::string code = R"CODE(// Automatically generated by the Fast Binary Encoding compiler, do not modify!
// https://github.com/chronoxor/FastBinaryEncoding
// Source: _INPUT_
// Version: _VERSION_
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_INPUT_"), source);
    code = std::regex_replace(code, std::regex("_VERSION_"), version);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorJava::GenerateFooter()
{
}

void GeneratorJava::GenerateImports(const std::string& domain, const std::string& package)
{
    // Generate package name
    WriteLine();
    WriteLineIndent("package " + domain + package + ";");
}

void GeneratorJava::GenerateImports(const std::shared_ptr<Package>& p)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";

    // Generate common import
    GenerateImports(domain, *p->name);
}

void GeneratorJava::GenerateFBEPackage(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Create FBE package path
    CppCommon::Directory::CreateTree(path);
}

void GeneratorJava::GenerateFBEPair(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "Pair.java";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding pair wrapper
public final class Pair<K, V>
{
    private final K key;
    private final V value;

    // Initialize the pair with given key and value
    public Pair(K key, V value)
    {
        this.key = key;
        this.value = value;
    }

    // Get the pair key
    public K getKey() { return key; }
    // Get the pair value
    public V getValue() { return value; }

    // Create a new pair
    public static <K, V> Pair<K, V> create(K key, V value) { return new Pair<K, V>(key, value); }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateFBEUUIDGenerator(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "UUIDGenerator.java";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding UUID generator
public final class UUIDGenerator
{
    // Gregorian epoch
    private static final long GregorianEpoch = -12219292800000L;

    // Lock and random generator
    private static final Object _lock = new Object();
    private static final java.util.Random _generator = new java.util.Random();

    // Node & clock sequence bytes
    private static long _node = makeNode();
    private static long _nodeAndClockSequence = makeNodeAndClockSequence();

    // Last UUID generated timestamp
    private static long _last = GregorianEpoch;

    private static long makeNode()
    {
        return _generator.nextLong() | 0x0000010000000000L;
    }

    private static long makeNodeAndClockSequence()
    {
        long clock = _generator.nextLong();

        long lsb = 0;
        // Variant (2 bits)
        lsb |= 0x8000000000000000L;
        // Clock sequence (14 bits)
        lsb |= (clock & 0x0000000000003FFFL) << 48;
        // 6 bytes
        lsb |= _node;
        return lsb;
    }

    // Generate nil UUID0 (all bits set to zero)
    public static java.util.UUID nil() { return new java.util.UUID(0, 0); }

    // Generate sequential UUID1 (time based version)
    public static java.util.UUID sequential()
    {
        long now = System.currentTimeMillis();

        // Generate new clock sequence bytes to get rid of UUID duplicates
        synchronized(_lock)
        {
            if (now <= _last)
                _nodeAndClockSequence = makeNodeAndClockSequence();
            _last = now;
        }

        long nanosSince = (now - GregorianEpoch) * 10000;

        long msb = 0L;
        msb |= (0x00000000FFFFFFFFL & nanosSince) << 32;
        msb |= (0x0000FFFF00000000L & nanosSince) >>> 16;
        msb |= (0xFFFF000000000000L & nanosSince) >>> 48;
        // Sets the version to 1
        msb |= 0x0000000000001000L;

        return new java.util.UUID(msb, _nodeAndClockSequence);
    }

    // Generate random UUID4 (randomly or pseudo-randomly generated version)
    public static java.util.UUID random() { return java.util.UUID.randomUUID(); }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateFBEBuffer(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "Buffer.java";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding buffer based on dynamic byte array
public class Buffer
{
    private byte[] _data;
    private long _size;
    private long _offset;

    // Is the buffer empty?
    public boolean isEmpty() { return (_data == null) || (_size == 0); }
    // Get bytes memory buffer
    public byte[] getData() { return _data; }
    // Get bytes memory buffer capacity
    public long getCapacity() { return _data.length; }
    // Get bytes memory buffer size
    public long getSize() { return _size; }
    // Get bytes memory buffer offset
    public long getOffset() { return _offset; }

    // Initialize a new expandable buffer with zero capacity
    public Buffer() { attach(); }
    // Initialize a new expandable buffer with the given capacity
    public Buffer(long capacity) { attach(capacity); }
    // Initialize a new buffer based on the specified byte array
    public Buffer(byte[] buffer) { attach(buffer); }
    // Initialize a new buffer based on the specified region (offset) of a byte array
    public Buffer(byte[] buffer, long offset) { attach(buffer, offset); }
    // Initialize a new buffer based on the specified region (size and offset) of a byte array
    public Buffer(byte[] buffer, long size, long offset) { attach(buffer, size, offset); }

    // Attach memory buffer methods
    public void attach() { _data = new byte[0]; _size = 0; _offset = 0; }
    public void attach(long capacity) { _data = new byte[(int)capacity]; _size = 0; _offset = 0; }
    public void attach(byte[] buffer) { _data = buffer; _size = buffer.length; _offset = 0; }
    public void attach(byte[] buffer, long offset) { _data = buffer; _size = buffer.length; _offset = offset; }
    public void attach(byte[] buffer, long size, long offset) { _data = buffer; _size = size; _offset = offset; }

    // Allocate memory in the current buffer and return offset to the allocated memory block
    public long allocate(long size)
    {
        assert (size >= 0) : "Invalid allocation size!";
        if (size < 0)
            throw new IllegalArgumentException("Invalid allocation size!");

        long offset = _size;

        // Calculate a new buffer size
        long total = _size + size;

        if (total <= _data.length)
        {
            _size = total;
            return offset;
        }

        byte[] data = new byte[(int)Math.max(total, 2 * _data.length)];
        System.arraycopy(_data, 0, data, 0, (int)_size);
        _data = data;
        _size = total;
        return offset;
    }

    // Remove some memory of the given size from the current buffer
    public void remove(long offset, long size)
    {
        assert ((offset + size) <= _size) : "Invalid offset & size!";
        if ((offset + size) > _size)
            throw new IllegalArgumentException("Invalid offset & size!");

        System.arraycopy(_data, (int)(offset + size), _data, (int)offset, (int)(_size - size - offset));
        _size -= size;
        if (_offset >= (offset + size))
            _offset -= size;
        else if (_offset >= offset)
        {
            _offset -= _offset - offset;
            if (_offset > _size)
                _offset = _size;
        }
    }

    // Reserve memory of the given capacity in the current buffer
    public void reserve(long capacity)
    {
        assert (capacity >= 0) : "Invalid reserve capacity!";
        if (capacity < 0)
            throw new IllegalArgumentException("Invalid reserve capacity!");

        if (capacity > _data.length)
        {
            byte[] data = new byte[(int)Math.max(capacity, 2 * _data.length)];
            System.arraycopy(_data, 0, data, 0, (int)_size);
            _data = data;
        }
    }

    // Resize the current buffer
    public void resize(long size)
    {
        reserve(size);
        _size = size;
        if (_offset > _size)
            _offset = _size;
    }

    // Reset the current buffer and its offset
    public void reset()
    {
        _size = 0;
        _offset = 0;
    }

    // Shift the current buffer offset
    public void shift(long offset) { _offset += offset; }
    // Unshift the current buffer offset
    public void unshift(long offset) { _offset -= offset; }

    // Buffer I/O methods

    public static boolean readBoolean(byte[] buffer, long offset)
    {
        return buffer[(int)offset] != 0;
    }

    public static byte readByte(byte[] buffer, long offset)
    {
        return buffer[(int)offset];
    }

    public static char readChar(byte[] buffer, long offset)
    {
        return (char)readInt8(buffer, offset);
    }

    public static char readWChar(byte[] buffer, long offset)
    {
        return (char)readInt32(buffer, offset);
    }

    public static byte readInt8(byte[] buffer, long offset)
    {
        return buffer[(int)offset];
    }

    public static short readInt16(byte[] buffer, long offset)
    {
        return (short)(((buffer[(int)offset + 0] & 0xFF) << 0) | ((buffer[(int)offset + 1] & 0xFF) << 8));
    }

    public static int readInt32(byte[] buffer, long offset)
    {
        return ((buffer[(int)offset + 0] & 0xFF) <<  0)|
               ((buffer[(int)offset + 1] & 0xFF) <<  8)|
               ((buffer[(int)offset + 2] & 0xFF) << 16)|
               ((buffer[(int)offset + 3] & 0xFF) << 24);
    }

    public static long readInt64(byte[] buffer, long offset)
    {
        return (((long)buffer[(int)offset + 0] & 0xFF) <<  0)|
               (((long)buffer[(int)offset + 1] & 0xFF) <<  8)|
               (((long)buffer[(int)offset + 2] & 0xFF) << 16)|
               (((long)buffer[(int)offset + 3] & 0xFF) << 24)|
               (((long)buffer[(int)offset + 4] & 0xFF) << 32)|
               (((long)buffer[(int)offset + 5] & 0xFF) << 40)|
               (((long)buffer[(int)offset + 6] & 0xFF) << 48)|
               (((long)buffer[(int)offset + 7] & 0xFF) << 56);
    }

    private static long readInt64BE(byte[] buffer, long offset)
    {
        return (((long)buffer[(int)offset + 0] & 0xFF) << 56)|
               (((long)buffer[(int)offset + 1] & 0xFF) << 48)|
               (((long)buffer[(int)offset + 2] & 0xFF) << 40)|
               (((long)buffer[(int)offset + 3] & 0xFF) << 32)|
               (((long)buffer[(int)offset + 4] & 0xFF) << 24)|
               (((long)buffer[(int)offset + 5] & 0xFF) << 16)|
               (((long)buffer[(int)offset + 6] & 0xFF) <<  8)|
               (((long)buffer[(int)offset + 7] & 0xFF) <<  0);
    }

    public static float readFloat(byte[] buffer, long offset)
    {
        int bits = readInt32(buffer, offset);
        return Float.intBitsToFloat(bits);
    }

    public static double readDouble(byte[] buffer, long offset)
    {
        long bits = readInt64(buffer, offset);
        return Double.longBitsToDouble(bits);
    }

    public static byte[] readBytes(byte[] buffer, long offset, long size)
    {
        byte[] result = new byte[(int)size];
        System.arraycopy(buffer, (int)offset, result, 0, (int)size);
        return result;
    }

    public static String readString(byte[] buffer, long offset, long size)
    {
        return new String(buffer, (int)offset, (int)size, java.nio.charset.StandardCharsets.UTF_8);
    }

    public static java.util.UUID readUUID(byte[] buffer, long offset)
    {
        return new java.util.UUID(readInt64BE(buffer, offset), readInt64BE(buffer, offset + 8));
    }

    public static void write(byte[] buffer, long offset, boolean value)
    {
        buffer[(int)offset] = (byte)(value ? 1 : 0);
    }

    public static void write(byte[] buffer, long offset, byte value)
    {
        buffer[(int)offset] = value;
    }

    public static void write(byte[] buffer, long offset, short value)
    {
        buffer[(int)offset + 0] = (byte)(value >>  0);
        buffer[(int)offset + 1] = (byte)(value >>  8);
    }

    public static void write(byte[] buffer, long offset, int value)
    {
        buffer[(int)offset + 0] = (byte)(value >>  0);
        buffer[(int)offset + 1] = (byte)(value >>  8);
        buffer[(int)offset + 2] = (byte)(value >> 16);
        buffer[(int)offset + 3] = (byte)(value >> 24);
    }

    public static void write(byte[] buffer, long offset, long value)
    {
        buffer[(int)offset + 0] = (byte)(value >>  0);
        buffer[(int)offset + 1] = (byte)(value >>  8);
        buffer[(int)offset + 2] = (byte)(value >> 16);
        buffer[(int)offset + 3] = (byte)(value >> 24);
        buffer[(int)offset + 4] = (byte)(value >> 32);
        buffer[(int)offset + 5] = (byte)(value >> 40);
        buffer[(int)offset + 6] = (byte)(value >> 48);
        buffer[(int)offset + 7] = (byte)(value >> 56);
    }

    private static void writeBE(byte[] buffer, long offset, long value)
    {
        buffer[(int)offset + 0] = (byte)(value >> 56);
        buffer[(int)offset + 1] = (byte)(value >> 48);
        buffer[(int)offset + 2] = (byte)(value >> 40);
        buffer[(int)offset + 3] = (byte)(value >> 32);
        buffer[(int)offset + 4] = (byte)(value >> 24);
        buffer[(int)offset + 5] = (byte)(value >> 16);
        buffer[(int)offset + 6] = (byte)(value >>  8);
        buffer[(int)offset + 7] = (byte)(value >>  0);
    }

    public static void write(byte[] buffer, long offset, float value)
    {
        int bits = Float.floatToIntBits(value);
        write(buffer, offset, bits);
    }

    public static void write(byte[] buffer, long offset, double value)
    {
        long bits = Double.doubleToLongBits(value);
        write(buffer, offset, bits);
    }

    public static void write(byte[] buffer, long offset, byte[] value)
    {
        System.arraycopy(value, 0, buffer, (int)offset, value.length);
    }

    public static void write(byte[] buffer, long offset, byte[] value, long valueOffset, long valueSize)
    {
        System.arraycopy(value, (int)valueOffset, buffer, (int)offset, (int)valueSize);
    }

    public static void write(byte[] buffer, long offset, byte value, long valueCount)
    {
        for (long i = 0; i < valueCount; i++)
            buffer[(int)(offset + i)] = value;
    }

    public static void write(byte[] buffer, long offset, java.util.UUID value)
    {
        writeBE(buffer, offset, value.getMostSignificantBits());
        writeBE(buffer, offset + 8, value.getLeastSignificantBits());
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateFBEModel(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "Model.java";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding base model
public class Model
{
    private Buffer _buffer;

    // Get bytes buffer
    public Buffer getBuffer() { return _buffer; }

    // Initialize a new model
    protected Model() { _buffer = new Buffer(); }
    protected Model(Buffer buffer) { _buffer = buffer; }

    // Attach memory buffer methods
    public void attach() { _buffer.attach(); }
    public void attach(long capacity) { _buffer.attach(capacity); }
    public void attach(byte[] buffer) { _buffer.attach(buffer); }
    public void attach(byte[] buffer, long offset) { _buffer.attach(buffer, offset); }
    public void attach(byte[] buffer, long size, long offset) { _buffer.attach(buffer, size, offset); }
    public void attach(Buffer buffer) { _buffer.attach(buffer.getData(), buffer.getSize(), buffer.getOffset()); }
    public void attach(Buffer buffer, long offset) { _buffer.attach(buffer.getData(), buffer.getSize(), offset); }

    // Model buffer operations
    public long allocate(long size) { return _buffer.allocate(size); }
    public void remove(long offset, long size) { _buffer.remove(offset, size); }
    public void reserve(long capacity) { _buffer.reserve(capacity); }
    public void resize(long size) { _buffer.resize(size); }
    public void reset() { _buffer.reset(); }
    public void shift(long offset) { _buffer.shift(offset); }
    public void unshift(long offset) { _buffer.unshift(offset); }

    // Buffer I/O methods
    protected int readInt32(long offset) { return Buffer.readInt32(_buffer.getData(), _buffer.getOffset() + offset); }
    protected void write(long offset, int value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateFBEFieldModel(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "FieldModel.java";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding base field model
public abstract class FieldModel
{
    protected Buffer _buffer;
    protected long _offset;

    // Get the field offset
    public long fbeOffset() { return _offset; }
    // Set the field offset
    public void fbeOffset(long value) { _offset = value; }

    // Get the field size
    public long fbeSize() { return 0; }
    // Get the field extra size
    public long fbeExtra() { return 0; }

    // Shift the current field offset
    public void fbeShift(long size) { _offset += size; }
    // Unshift the current field offset
    public void fbeUnshift(long size) { _offset -= size; }

    // Initialize a new field model
    protected FieldModel(Buffer buffer, long offset)
    {
        _buffer = buffer;
        _offset = offset;
    }

    // Check if the value is valid
    public boolean verify() { return true; }

    // Buffer I/O methods
    protected boolean readBoolean(long offset) { return Buffer.readBoolean(_buffer.getData(), _buffer.getOffset() + offset); }
    protected byte readByte(long offset) { return Buffer.readByte(_buffer.getData(), _buffer.getOffset() + offset); }
    protected char readChar(long offset) { return Buffer.readChar(_buffer.getData(), _buffer.getOffset() + offset); }
    protected char readWChar(long offset) { return Buffer.readWChar(_buffer.getData(), _buffer.getOffset() + offset); }
    protected byte readInt8(long offset) { return Buffer.readInt8(_buffer.getData(), _buffer.getOffset() + offset); }
    protected short readInt16(long offset) { return Buffer.readInt16(_buffer.getData(), _buffer.getOffset() + offset); }
    protected int readInt32(long offset) { return Buffer.readInt32(_buffer.getData(), _buffer.getOffset() + offset); }
    protected long readInt64(long offset) { return Buffer.readInt64(_buffer.getData(), _buffer.getOffset() + offset); }
    protected float readFloat(long offset) { return Buffer.readFloat(_buffer.getData(), _buffer.getOffset() + offset); }
    protected double readDouble(long offset) { return Buffer.readDouble(_buffer.getData(), _buffer.getOffset() + offset); }
    protected byte[] readBytes(long offset, long size) { return Buffer.readBytes(_buffer.getData(), _buffer.getOffset() + offset, size); }
    protected String readString(long offset, long size) { return Buffer.readString(_buffer.getData(), _buffer.getOffset() + offset, size); }
    protected java.util.UUID readUUID(long offset) { return Buffer.readUUID(_buffer.getData(), _buffer.getOffset() + offset); }
    protected void write(long offset, boolean value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
    protected void write(long offset, byte value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
    protected void write(long offset, short value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
    protected void write(long offset, int value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
    protected void write(long offset, long value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
    protected void write(long offset, float value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
    protected void write(long offset, double value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
    protected void write(long offset, byte[] value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
    protected void write(long offset, byte[] value, long valueOffset, long valueSize) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value, valueOffset, valueSize); }
    protected void write(long offset, byte value, long valueCount) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value, valueCount); }
    protected void write(long offset, java.util.UUID value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateFBEFieldModel(const std::string& domain, const std::string& package, const std::string& name, const std::string& type, const std::string& base, const std::string& size, const std::string& defaults)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / ("FieldModel" + name + ".java");
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding _TYPE_ field model
public final class FieldModel_NAME_ extends FieldModel
{
    public FieldModel_NAME_(Buffer buffer, long offset) { super(buffer, offset); }

    // Get the field size
    @Override
    public long fbeSize() { return _SIZE_; }

    // Get the value
    public _TYPE_ get() { return get(_DEFAULTS_); }
    public _TYPE_ get(_TYPE_ defaults)
    {
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return defaults;

        return read_NAME_(fbeOffset());
    }

    // Set the value
    public void set(_TYPE_ value)
    {
        assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return;

        write(fbeOffset(), _BASE_value);
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_NAME_"), name);
    code = std::regex_replace(code, std::regex("_TYPE_"), type);
    code = std::regex_replace(code, std::regex("_BASE_"), base);
    code = std::regex_replace(code, std::regex("_SIZE_"), size);
    code = std::regex_replace(code, std::regex("_DEFAULTS_"), defaults);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateFBEFieldModelDecimal(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "FieldModelDecimal.java";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding decimal field model
public final class FieldModelDecimal extends FieldModel
{
    public FieldModelDecimal(Buffer buffer, long offset) { super(buffer, offset); }

    // Get the field size
    @Override
    public long fbeSize() { return 16; }

    // Get the decimal value
    public java.math.BigDecimal get() { return get(java.math.BigDecimal.valueOf(0L)); }
    public java.math.BigDecimal get(java.math.BigDecimal defaults)
    {
        assert (defaults != null) : "Invalid default decimal value!";
        if (defaults == null)
            throw new IllegalArgumentException("Invalid default decimal value!");

        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return defaults;

        byte[] magnitude = readBytes(fbeOffset(), 12);
        int scale = readByte(fbeOffset() + 14);
        int signum = (readByte(fbeOffset() + 15) < 0) ? -1 : 1;

        // Reverse magnitude
        for(int i = 0; i < magnitude.length / 2; i++)
        {
            byte temp = magnitude[i];
            magnitude[i] = magnitude[magnitude.length - i - 1];
            magnitude[magnitude.length - i - 1] = temp;
        }

        var unscaled = new java.math.BigInteger(signum, magnitude);

        return new java.math.BigDecimal(unscaled, scale);
    }

    // Set the decimal value
    public void set(java.math.BigDecimal value)
    {
        assert (value != null) : "Invalid decimal value!";
        if (value == null)
            throw new IllegalArgumentException("Invalid decimal value!");

        assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return;

        // Get unscaled absolute value
        java.math.BigInteger unscaled = value.abs().unscaledValue();
        int bitLength = unscaled.bitLength();
        if ((bitLength < 0) || (bitLength > 96))
        {
            // Value too big for .NET Decimal (bit length is limited to [0, 96])
            write(fbeOffset(), (byte)0, fbeSize());
            return;
        }

        // Get byte array
        byte[] unscaledBytes = unscaled.toByteArray();

        // Get scale
        int scale = value.scale();
        if ((scale < 0) || (scale > 28))
        {
            // Value scale exceeds .NET Decimal limit of [0, 28]
            write(fbeOffset(), (byte)0, fbeSize());
            return;
        }

        // Write unscaled value to bytes 0-11
        int index = 0;
        for (int i = unscaledBytes.length - 1; (i >= 0) && (index < 12); i--, index++)
            write(fbeOffset() + index, unscaledBytes[i]);

        // Fill remaining bytes with zeros
        for (; index < 14; index++)
            write(fbeOffset() + index, (byte)0);

        // Write scale at byte 14
        write(fbeOffset() + 14, (byte)scale);

        // Write signum at byte 15
        write(fbeOffset() + 15, (byte)((value.signum() < 0) ? -128 : 0));
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateFBEFieldModelDate(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "FieldModelDate.java";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding date field model
public final class FieldModelDate extends FieldModel
{
    public FieldModelDate(Buffer buffer, long offset) { super(buffer, offset); }

    // Get the field size
    @Override
    public long fbeSize() { return 8; }

    // Get the date value
    public java.util.Date get() { return get(new java.util.Date(0)); }
    public java.util.Date get(java.util.Date defaults)
    {
        assert (defaults != null) : "Invalid default date value!";
        if (defaults == null)
            throw new IllegalArgumentException("Invalid default date value!");

        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return defaults;

        long nanoseconds = readInt64(fbeOffset());
        return new java.util.Date(nanoseconds / 1000000);
    }

    // Set the date value
    public void set(java.util.Date value)
    {
        assert (value != null) : "Invalid date value!";
        if (value == null)
            throw new IllegalArgumentException("Invalid date value!");

        assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return;

        long nanoseconds = value.getTime() * 1000;
        write(fbeOffset(), nanoseconds);
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateFBEFieldModelTimestamp(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "FieldModelTimestamp.java";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding timestamp field model
public final class FieldModelTimestamp extends FieldModel
{
    public FieldModelTimestamp(Buffer buffer, long offset) { super(buffer, offset); }

    // Get the field size
    @Override
    public long fbeSize() { return 8; }

    // Get the timestamp value
    public java.time.Instant get() { return get(java.time.Instant.EPOCH); }
    public java.time.Instant get(java.time.Instant defaults)
    {
        assert (defaults != null) : "Invalid default timestamp value!";
        if (defaults == null)
            throw new IllegalArgumentException("Invalid default timestamp value!");

        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return defaults;

        long nanoseconds = readInt64(fbeOffset());
        return java.time.Instant.ofEpochSecond(nanoseconds / 1000000000, nanoseconds % 1000000000);
    }

    // Set the timestamp value
    public void set(java.time.Instant value)
    {
        assert (value != null) : "Invalid timestamp value!";
        if (value == null)
            throw new IllegalArgumentException("Invalid timestamp value!");

        assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return;

        long nanoseconds = value.getEpochSecond() * 1000000000 + value.getNano();
        write(fbeOffset(), nanoseconds);
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateFBEFieldModelBytes(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "FieldModelBytes.java";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding bytes field model
public final class FieldModelBytes extends FieldModel
{
    public FieldModelBytes(Buffer buffer, long offset) { super(buffer, offset); }

    // Get the field size
    @Override
    public long fbeSize() { return 4; }
    // Get the field extra size
    @Override
    public long fbeExtra()
    {
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return 0;

        int fbeBytesOffset = readInt32(fbeOffset());
        if ((fbeBytesOffset == 0) || ((_buffer.getOffset() + fbeBytesOffset + 4) > _buffer.getSize()))
            return 0;

        int fbeBytesSize = readInt32(fbeBytesOffset);
        return 4 + fbeBytesSize;
    }

    // Check if the bytes value is valid
    @Override
    public boolean verify()
    {
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return true;

        int fbeBytesOffset = readInt32(fbeOffset());
        if (fbeBytesOffset == 0)
            return true;

        if ((_buffer.getOffset() + fbeBytesOffset + 4) > _buffer.getSize())
            return false;

        int fbeBytesSize = readInt32(fbeBytesOffset);
        if ((_buffer.getOffset() + fbeBytesOffset + 4 + fbeBytesSize) > _buffer.getSize())
            return false;

        return true;
    }

    // Get the bytes value
    public java.nio.ByteBuffer get() { return get(java.nio.ByteBuffer.allocate(0)); }
    public java.nio.ByteBuffer get(java.nio.ByteBuffer defaults)
    {
        assert (defaults != null) : "Invalid default bytes value!";
        if (defaults == null)
            throw new IllegalArgumentException("Invalid default bytes value!");

        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return defaults;

        int fbeBytesOffset = readInt32(fbeOffset());
        if (fbeBytesOffset == 0)
            return defaults;

        assert ((_buffer.getOffset() + fbeBytesOffset + 4) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeBytesOffset + 4) > _buffer.getSize())
            return defaults;

        int fbeBytesSize = readInt32(fbeBytesOffset);
        assert ((_buffer.getOffset() + fbeBytesOffset + 4 + fbeBytesSize) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeBytesOffset + 4 + fbeBytesSize) > _buffer.getSize())
            return defaults;

        return java.nio.ByteBuffer.wrap(readBytes(fbeBytesOffset + 4, fbeBytesSize));
    }

    // Set the bytes value
    public void set(java.nio.ByteBuffer value)
    {
        assert (value != null) : "Invalid bytes value!";
        if (value == null)
            throw new IllegalArgumentException("Invalid bytes value!");

        assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return;

        int fbeBytesSize = value.array().length;
        int fbeBytesOffset = (int)(_buffer.allocate(4 + fbeBytesSize) - _buffer.getOffset());
        assert ((fbeBytesOffset > 0) && ((_buffer.getOffset() + fbeBytesOffset + 4 + fbeBytesSize) <= _buffer.getSize())) : "Model is broken!";
        if ((fbeBytesOffset <= 0) || ((_buffer.getOffset() + fbeBytesOffset + 4 + fbeBytesSize) > _buffer.getSize()))
            return;

        write(fbeOffset(), fbeBytesOffset);
        write(fbeBytesOffset, fbeBytesSize);
        write(fbeBytesOffset + 4, value.array());
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateFBEFieldModelString(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "FieldModelString.java";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding string field model
public final class FieldModelString extends FieldModel
{
    public FieldModelString(Buffer buffer, long offset) { super(buffer, offset); }

    // Get the field size
    @Override
    public long fbeSize() { return 4; }
    // Get the field extra size
    @Override
    public long fbeExtra()
    {
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return 0;

        int fbeStringOffset = readInt32(fbeOffset());
        if ((fbeStringOffset == 0) || ((_buffer.getOffset() + fbeStringOffset + 4) > _buffer.getSize()))
            return 0;

        int fbeStringSize = readInt32(fbeStringOffset);
        return 4 + fbeStringSize;
    }

    // Check if the string value is valid
    @Override
    public boolean verify()
    {
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return true;

        int fbeStringOffset = readInt32(fbeOffset());
        if (fbeStringOffset == 0)
            return true;

        if ((_buffer.getOffset() + fbeStringOffset + 4) > _buffer.getSize())
            return false;

        int fbeStringSize = readInt32(fbeStringOffset);
        if ((_buffer.getOffset() + fbeStringOffset + 4 + fbeStringSize) > _buffer.getSize())
            return false;

        return true;
    }

    // Get the string value
    public String get() { return get(""); }
    public String get(String defaults)
    {
        assert (defaults != null) : "Invalid default string value!";
        if (defaults == null)
            throw new IllegalArgumentException("Invalid default string value!");

        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return defaults;

        int fbeStringOffset = readInt32(fbeOffset());
        if (fbeStringOffset == 0)
            return defaults;

        assert ((_buffer.getOffset() + fbeStringOffset + 4) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeStringOffset + 4) > _buffer.getSize())
            return defaults;

        int fbeStringSize = readInt32(fbeStringOffset);
        assert ((_buffer.getOffset() + fbeStringOffset + 4 + fbeStringSize) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeStringOffset + 4 + fbeStringSize) > _buffer.getSize())
            return defaults;

        return readString(fbeStringOffset + 4, fbeStringSize);
    }

    // Set the string value
    public void set(String value)
    {
        assert (value != null) : "Invalid string value!";
        if (value == null)
            throw new IllegalArgumentException("Invalid string value!");

        assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return;

        byte[] bytes = value.getBytes(java.nio.charset.StandardCharsets.UTF_8);

        int fbeStringSize = bytes.length;
        int fbeStringOffset = (int)(_buffer.allocate(4 + fbeStringSize) - _buffer.getOffset());
        assert ((fbeStringOffset > 0) && ((_buffer.getOffset() + fbeStringOffset + 4 + fbeStringSize) <= _buffer.getSize())) : "Model is broken!";
        if ((fbeStringOffset <= 0) || ((_buffer.getOffset() + fbeStringOffset + 4 + fbeStringSize) > _buffer.getSize()))
            return;

        write(fbeOffset(), fbeStringOffset);
        write(fbeStringOffset, fbeStringSize);
        write(fbeStringOffset + 4, bytes);
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateFBEFieldModelOptional(const std::string& domain, const std::string& package, const std::string& name, const std::string& type, const std::string& model)
{
    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the file
    CppCommon::Path file = path / ("FieldModelOptional" + name + ".java");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    std::string code = R"CODE(
// Fast Binary Encoding optional _NAME_ field model
public final class FieldModelOptional_NAME_ extends _DOMAIN_fbe.FieldModel
{
    public FieldModelOptional_NAME_(_DOMAIN_fbe.Buffer buffer, long offset)
    {
        super(buffer, offset);
        value = new _MODEL_(buffer, 0);
    }

    // Get the field size
    @Override
    public long fbeSize() { return 1 + 4; }
    // Get the field extra size
    @Override
    public long fbeExtra()
    {
        if (!hasValue())
            return 0;

        int fbeOptionalOffset = readInt32(fbeOffset() + 1);
        if ((fbeOptionalOffset == 0) || ((_buffer.getOffset() + fbeOptionalOffset + 4) > _buffer.getSize()))
            return 0;

        _buffer.shift(fbeOptionalOffset);
        long fbeResult = value.fbeSize() + value.fbeExtra();
        _buffer.unshift(fbeOptionalOffset);
        return fbeResult;
    }

    // Checks if the object contains a value
    public boolean hasValue()
    {
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return false;

        byte fbeHasValue = readInt8(fbeOffset());
        return (fbeHasValue != 0);
    }

    // Base field model value
    public final _MODEL_ value;

    // Check if the optional value is valid
    @Override
    public boolean verify()
    {
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return true;

        byte fbeHasValue = readInt8(fbeOffset());
        if (fbeHasValue == 0)
            return true;

        int fbeOptionalOffset = readInt32(fbeOffset() + 1);
        if (fbeOptionalOffset == 0)
            return false;

        _buffer.shift(fbeOptionalOffset);
        boolean fbeResult = value.verify();
        _buffer.unshift(fbeOptionalOffset);
        return fbeResult;
    }

    // Get the optional value (being phase)
    public long getBegin()
    {
        if (!hasValue())
            return 0;

        int fbeOptionalOffset = readInt32(fbeOffset() + 1);
        assert (fbeOptionalOffset > 0) : "Model is broken!";
        if (fbeOptionalOffset <= 0)
            return 0;

        _buffer.shift(fbeOptionalOffset);
        return fbeOptionalOffset;
    }

    // Get the optional value (end phase)
    public void getEnd(long fbeBegin)
    {
        _buffer.unshift(fbeBegin);
    }

    // Get the optional value
    public _TYPE_ get() { return get(null); }
    public _TYPE_ get(_TYPE_ defaults)
    {
        long fbeBegin = getBegin();
        if (fbeBegin == 0)
            return defaults;

        _TYPE_ optional = value.get();
        getEnd(fbeBegin);
        return optional;
    }

    // Set the optional value (begin phase)
    public long setBegin(boolean hasValue)
    {
        assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return 0;

        byte fbeHasValue = (byte)(hasValue ? 1 : 0);
        write(fbeOffset(), fbeHasValue);
        if (fbeHasValue == 0)
            return 0;

        int fbeOptionalSize = (int)value.fbeSize();
        int fbeOptionalOffset = (int)(_buffer.allocate(fbeOptionalSize) - _buffer.getOffset());
        assert ((fbeOptionalOffset > 0) && ((_buffer.getOffset() + fbeOptionalOffset + fbeOptionalSize) <= _buffer.getSize())) : "Model is broken!";
        if ((fbeOptionalOffset <= 0) || ((_buffer.getOffset() + fbeOptionalOffset + fbeOptionalSize) > _buffer.getSize()))
            return 0;

        write(fbeOffset() + 1, fbeOptionalOffset);

        _buffer.shift(fbeOptionalOffset);
        return fbeOptionalOffset;
    }

    // Set the optional value (end phase)
    public void setEnd(long fbeBegin)
    {
        _buffer.unshift(fbeBegin);
    }

    // Set the optional value
    public void set(_TYPE_ optional)
    {
        long fbeBegin = setBegin(optional != null);
        if (fbeBegin == 0)
            return;

        value.set(optional);
        setEnd(fbeBegin);
    }
}
)CODE";

    std::string type_name = IsPackageType(type) ? type : (domain + package + "." + type);

    // Prepare code template
    code = std::regex_replace(code, std::regex("_DOMAIN_"), domain);
    code = std::regex_replace(code, std::regex("_NAME_"), name);
    code = std::regex_replace(code, std::regex("_TYPE_"), type_name);
    code = std::regex_replace(code, std::regex("_MODEL_"), model);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateFBEFieldModelArray(const std::string& domain, const std::string& package, const std::string& name, const std::string& type, const std::string& model, bool bytes)
{
    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the file
    CppCommon::Path file = path / ("FieldModelArray" + name + ".java");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    std::string code = R"CODE(
// Fast Binary Encoding _NAME_ array field model
public final class FieldModelArray_NAME_ extends _DOMAIN_fbe.FieldModel
{
    private final _MODEL_ _model;
    private final long _size;

    public FieldModelArray_NAME_(_DOMAIN_fbe.Buffer buffer, long offset, long size)
    {
        super(buffer, offset);
        _model = new _MODEL_(buffer, offset);
        _size = size;
    }

    // Get the field size
    @Override
    public long fbeSize() { return _size * _model.fbeSize(); }
    // Get the field extra size
    @Override
    public long fbeExtra() { return 0; }

    // Get the array offset
    public long getOffset() { return 0; }
    // Get the array size
    public long getSize() { return _size; }

    // Array index operator
    public _MODEL_ getItem(long index)
    {
        assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
        assert (index < _size) : "Index is out of bounds!";

        _model.fbeOffset(fbeOffset());
        _model.fbeShift(index * _model.fbeSize());
        return _model;
    }

    // Check if the array is valid
    @Override
    public boolean verify()
    {
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return false;

        _model.fbeOffset(fbeOffset());
        for (long i = _size; i-- > 0;)
        {
            if (!_model.verify())
                return false;
            _model.fbeShift(_model.fbeSize());
        }

        return true;
    }

    // Get the array
    public _ARRAY_ get()
    {
        var values = _INIT_;

        var fbeModel = getItem(0);
        for (long i = 0; i < _size; i++)
        {
            values[(int)i] = fbeModel.get();
            fbeModel.fbeShift(fbeModel.fbeSize());
        }
        return values;
    }

    // Get the array
    public void get(_ARRAY_ values)
    {
        assert (values != null) : "Invalid values parameter!";
        if (values == null)
            throw new IllegalArgumentException("Invalid values parameter!");

        var fbeModel = getItem(0);
        for (long i = 0; (i < values.length) && (i < _size); i++)
        {
            values[(int)i] = fbeModel.get();
            fbeModel.fbeShift(fbeModel.fbeSize());
        }
    }

    // Get the array as java.util.ArrayList
    public void get(java.util.ArrayList<_TYPE_> values)
    {
        assert (values != null) : "Invalid values parameter!";
        if (values == null)
            throw new IllegalArgumentException("Invalid values parameter!");

        values.clear();
        values.ensureCapacity((int)_size);

        var fbeModel = getItem(0);
        for (long i = _size; i-- > 0;)
        {
            _TYPE_ value = fbeModel.get();
            values.add(value);
            fbeModel.fbeShift(fbeModel.fbeSize());
        }
    }

    // Set the array
    public void set(_ARRAY_ values)
    {
        assert (values != null) : "Invalid values parameter!";
        if (values == null)
            throw new IllegalArgumentException("Invalid values parameter!");

        assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return;

        var fbeModel = getItem(0);
        for (long i = 0; (i < values.length) && (i < _size); i++)
        {
            fbeModel.set(values[(int)i]);
            fbeModel.fbeShift(fbeModel.fbeSize());
        }
    }

    // Set the array as java.util.ArrayList
    public void set(java.util.ArrayList<_TYPE_> values)
    {
        assert (values != null) : "Invalid values parameter!";
        if (values == null)
            throw new IllegalArgumentException("Invalid values parameter!");

        assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return;

        var fbeModel = getItem(0);
        for (long i = 0; (i < values.size()) && (i < _size); i++)
        {
            fbeModel.set(values.get((int)i));
            fbeModel.fbeShift(fbeModel.fbeSize());
        }
    }
}
)CODE";

    std::string type_name = IsPackageType(type) ? type : (domain + package + "." + type);

    // Prepare code template
    code = std::regex_replace(code, std::regex("_DOMAIN_"), domain);
    code = std::regex_replace(code, std::regex("_NAME_"), name);
    code = std::regex_replace(code, std::regex("_TYPE_"), type_name);
    code = std::regex_replace(code, std::regex("_MODEL_"), model);
    code = std::regex_replace(code, std::regex("_ARRAY_"), (bytes ? "byte" : type_name) + "[]");
    code = std::regex_replace(code, std::regex("_INIT_"), ((type != "byte[]") ? ("new " + (bytes ? "byte" : type_name) + "[(int)_size]") : "new byte[(int)_size][]"));
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateFBEFieldModelVector(const std::string& domain, const std::string& package, const std::string& name, const std::string& type, const std::string& model)
{
    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the file
    CppCommon::Path file = path / ("FieldModelVector" + name + ".java");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    std::string code = R"CODE(
// Fast Binary Encoding _NAME_ vector field model
public final class FieldModelVector_NAME_ extends _DOMAIN_fbe.FieldModel
{
    private final _MODEL_ _model;

    public FieldModelVector_NAME_(_DOMAIN_fbe.Buffer buffer, long offset)
    {
        super(buffer, offset);
        _model = new _MODEL_(buffer, offset);
    }

    // Get the field size
    @Override
    public long fbeSize() { return 4; }
    // Get the field extra size
    @Override
    public long fbeExtra()
    {
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return 0;

        int fbeVectorOffset = readInt32(fbeOffset());
        if ((fbeVectorOffset == 0) || ((_buffer.getOffset() + fbeVectorOffset + 4) > _buffer.getSize()))
            return 0;

        int fbeVectorSize = readInt32(fbeVectorOffset);

        long fbeResult = 4;
        _model.fbeOffset(fbeVectorOffset + 4);
        for (int i = fbeVectorSize; i-- > 0;)
        {
            fbeResult += _model.fbeSize() + _model.fbeExtra();
            _model.fbeShift(_model.fbeSize());
        }
        return fbeResult;
    }

    // Get the vector offset
    public long getOffset()
    {
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return 0;

        int fbeVectorOffset = readInt32(fbeOffset());
        return fbeVectorOffset;
    }

    // Get the vector size
    public long getSize()
    {
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return 0;

        int fbeVectorOffset = readInt32(fbeOffset());
        if ((fbeVectorOffset == 0) || ((_buffer.getOffset() + fbeVectorOffset + 4) > _buffer.getSize()))
            return 0;

        int fbeVectorSize = readInt32(fbeVectorOffset);
        return fbeVectorSize;
    }

    // Vector index operator
    public _MODEL_ getItem(long index)
    {
        assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";

        int fbeVectorOffset = readInt32(fbeOffset());
        assert ((fbeVectorOffset > 0) && ((_buffer.getOffset() + fbeVectorOffset + 4) <= _buffer.getSize())) : "Model is broken!";

        int fbeVectorSize = readInt32(fbeVectorOffset);
        assert (index < fbeVectorSize) : "Index is out of bounds!";

        _model.fbeOffset(fbeVectorOffset + 4);
        _model.fbeShift(index * _model.fbeSize());
        return _model;
    }

    // Resize the vector and get its first model
    public _MODEL_ resize(long size)
    {
        int fbeVectorSize = (int)(size * _model.fbeSize());
        int fbeVectorOffset = (int)(_buffer.allocate(4 + fbeVectorSize) - _buffer.getOffset());
        assert ((fbeVectorOffset > 0) && ((_buffer.getOffset() + fbeVectorOffset + 4) <= _buffer.getSize())) : "Model is broken!";

        write(fbeOffset(), fbeVectorOffset);
        write(fbeVectorOffset, (int)size);
        write(fbeVectorOffset + 4, (byte)0, fbeVectorSize);

        _model.fbeOffset(fbeVectorOffset + 4);
        return _model;
    }

    // Check if the vector is valid
    @Override
    public boolean verify()
    {
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return true;

        int fbeVectorOffset = readInt32(fbeOffset());
        if (fbeVectorOffset == 0)
            return true;

        if ((_buffer.getOffset() + fbeVectorOffset + 4) > _buffer.getSize())
            return false;

        int fbeVectorSize = readInt32(fbeVectorOffset);

        _model.fbeOffset(fbeVectorOffset + 4);
        for (int i = fbeVectorSize; i-- > 0;)
        {
            if (!_model.verify())
                return false;
            _model.fbeShift(_model.fbeSize());
        }

        return true;
    }

    // Get the vector as java.util.ArrayList
    public void get(java.util.ArrayList<_TYPE_> values)
    {
        assert (values != null) : "Invalid values parameter!";
        if (values == null)
            throw new IllegalArgumentException("Invalid values parameter!");

        values.clear();

        long fbeVectorSize = getSize();
        if (fbeVectorSize == 0)
            return;

        values.ensureCapacity((int)fbeVectorSize);

        var fbeModel = getItem(0);
        for (long i = fbeVectorSize; i-- > 0;)
        {
            _TYPE_ value = fbeModel.get();
            values.add(value);
            fbeModel.fbeShift(fbeModel.fbeSize());
        }
    }

    // Get the vector as java.util.LinkedList
    public void get(java.util.LinkedList<_TYPE_> values)
    {
        assert (values != null) : "Invalid values parameter!";
        if (values == null)
            throw new IllegalArgumentException("Invalid values parameter!");

        values.clear();

        long fbeVectorSize = getSize();
        if (fbeVectorSize == 0)
            return;

        var fbeModel = getItem(0);
        for (long i = fbeVectorSize; i-- > 0;)
        {
            _TYPE_ value = fbeModel.get();
            values.add(value);
            fbeModel.fbeShift(fbeModel.fbeSize());
        }
    }

    // Get the vector as java.util.HashSet
    public void get(java.util.HashSet<_TYPE_> values)
    {
        assert (values != null) : "Invalid values parameter!";
        if (values == null)
            throw new IllegalArgumentException("Invalid values parameter!");

        values.clear();

        long fbeVectorSize = getSize();
        if (fbeVectorSize == 0)
            return;

        var fbeModel = getItem(0);
        for (long i = fbeVectorSize; i-- > 0;)
        {
            _TYPE_ value = fbeModel.get();
            values.add(value);
            fbeModel.fbeShift(fbeModel.fbeSize());
        }
    }

    // Set the vector as java.util.ArrayList
    public void set(java.util.ArrayList<_TYPE_> values)
    {
        assert (values != null) : "Invalid values parameter!";
        if (values == null)
            throw new IllegalArgumentException("Invalid values parameter!");

        assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return;

        var fbeModel = resize(values.size());
        for (var value : values)
        {
            fbeModel.set(value);
            fbeModel.fbeShift(fbeModel.fbeSize());
        }
    }

    // Set the vector as java.util.LinkedList
    public void set(java.util.LinkedList<_TYPE_> values)
    {
        assert (values != null) : "Invalid values parameter!";
        if (values == null)
            throw new IllegalArgumentException("Invalid values parameter!");

        assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return;

        var fbeModel = resize(values.size());
        for (var value : values)
        {
            fbeModel.set(value);
            fbeModel.fbeShift(fbeModel.fbeSize());
        }
    }

    // Set the vector as java.util.HashSet
    public void set(java.util.HashSet<_TYPE_> values)
    {
        assert (values != null) : "Invalid values parameter!";
        if (values == null)
            throw new IllegalArgumentException("Invalid values parameter!");

        assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return;

        var fbeModel = resize(values.size());
        for (var value : values)
        {
            fbeModel.set(value);
            fbeModel.fbeShift(fbeModel.fbeSize());
        }
    }
}
)CODE";

    std::string type_name = IsPackageType(type) ? type : (domain + package + "." + type);

    // Prepare code template
    code = std::regex_replace(code, std::regex("_DOMAIN_"), domain);
    code = std::regex_replace(code, std::regex("_NAME_"), name);
    code = std::regex_replace(code, std::regex("_TYPE_"), type_name);
    code = std::regex_replace(code, std::regex("_MODEL_"), model);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateFBEFieldModelMap(const std::string& domain, const std::string& package, const std::string& key_name, const std::string& key_type, const std::string& key_model, const std::string& value_name, const std::string& value_type, const std::string& value_model)
{
    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the file
    CppCommon::Path file = path / ("FieldModelMap" + key_name + value_name + ".java");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    std::string code = R"CODE(
// Fast Binary Encoding _KEY_NAME_->_VALUE_NAME_ map field model
public final class FieldModelMap_KEY_NAME__VALUE_NAME_ extends _DOMAIN_fbe.FieldModel
{
    private final _KEY_MODEL_ _modelKey;
    private final _VALUE_MODEL_ _modelValue;

    public FieldModelMap_KEY_NAME__VALUE_NAME_(_DOMAIN_fbe.Buffer buffer, long offset)
    {
        super(buffer, offset);
        _modelKey = new _KEY_MODEL_(buffer, offset);
        _modelValue = new _VALUE_MODEL_(buffer, offset);
    }

    // Get the field size
    @Override
    public long fbeSize() { return 4; }
    // Get the field extra size
    @Override
    public long fbeExtra()
    {
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return 0;

        int fbeMapOffset = readInt32(fbeOffset());
        if ((fbeMapOffset == 0) || ((_buffer.getOffset() + fbeMapOffset + 4) > _buffer.getSize()))
            return 0;

        int fbeMapSize = readInt32(fbeMapOffset);

        long fbeResult = 4;
        _modelKey.fbeOffset(fbeMapOffset + 4);
        _modelValue.fbeOffset(fbeMapOffset + 4 + _modelKey.fbeSize());
        for (int i = fbeMapSize; i-- > 0;)
        {
            fbeResult += _modelKey.fbeSize() + _modelKey.fbeExtra();
            _modelKey.fbeShift(_modelKey.fbeSize() + _modelValue.fbeSize());

            fbeResult += _modelValue.fbeSize() + _modelValue.fbeExtra();
            _modelValue.fbeShift(_modelKey.fbeSize() + _modelValue.fbeSize());
        }
        return fbeResult;
    }

    // Get the map offset
    public long getOffset()
    {
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return 0;

        int fbeMapOffset = readInt32(fbeOffset());
        return fbeMapOffset;
    }

    // Get the map size
    public long getSize()
    {
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return 0;

        int fbeMapOffset = readInt32(fbeOffset());
        if ((fbeMapOffset == 0) || ((_buffer.getOffset() + fbeMapOffset + 4) > _buffer.getSize()))
            return 0;

        int fbeMapSize = readInt32(fbeMapOffset);
        return fbeMapSize;
    }

    // Map index operator
    public _DOMAIN_fbe.Pair<_KEY_MODEL_, _VALUE_MODEL_> getItem(long index)
    {
        assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";

        int fbeMapOffset = readInt32(fbeOffset());
        assert ((fbeMapOffset > 0) && ((_buffer.getOffset() + fbeMapOffset + 4) <= _buffer.getSize())) : "Model is broken!";

        int fbeMapSize = readInt32(fbeMapOffset);
        assert (index < fbeMapSize) : "Index is out of bounds!";

        _modelKey.fbeOffset(fbeMapOffset + 4);
        _modelValue.fbeOffset(fbeMapOffset + 4 + _modelKey.fbeSize());
        _modelKey.fbeShift(index * (_modelKey.fbeSize() + _modelValue.fbeSize()));
        _modelValue.fbeShift(index * (_modelKey.fbeSize() + _modelValue.fbeSize()));
        return _DOMAIN_fbe.Pair.create(_modelKey, _modelValue);
    }

    // Resize the map and get its first model
    public _DOMAIN_fbe.Pair<_KEY_MODEL_, _VALUE_MODEL_> resize(long size)
    {
        int fbeMapSize = (int)(size * (_modelKey.fbeSize() + _modelValue.fbeSize()));
        int fbeMapOffset = (int)(_buffer.allocate(4 + fbeMapSize) - _buffer.getOffset());
        assert ((fbeMapOffset > 0) && ((_buffer.getOffset() + fbeMapOffset + 4) <= _buffer.getSize())) : "Model is broken!";

        write(fbeOffset(), fbeMapOffset);
        write(fbeMapOffset, (int)size);
        write(fbeMapOffset + 4, (byte)0, fbeMapSize);

        _modelKey.fbeOffset(fbeMapOffset + 4);
        _modelValue.fbeOffset(fbeMapOffset + 4 + _modelKey.fbeSize());
        return _DOMAIN_fbe.Pair.create(_modelKey, _modelValue);
    }

    // Check if the map is valid
    @Override
    public boolean verify()
    {
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return true;

        int fbeMapOffset = readInt32(fbeOffset());
        if (fbeMapOffset == 0)
            return true;

        if ((_buffer.getOffset() + fbeMapOffset + 4) > _buffer.getSize())
            return false;

        int fbeMapSize = readInt32(fbeMapOffset);

        _modelKey.fbeOffset(fbeMapOffset + 4);
        _modelValue.fbeOffset(fbeMapOffset + 4 + _modelKey.fbeSize());
        for (int i = fbeMapSize; i-- > 0;)
        {
            if (!_modelKey.verify())
                return false;
            _modelKey.fbeShift(_modelKey.fbeSize() + _modelValue.fbeSize());
            if (!_modelValue.verify())
                return false;
            _modelValue.fbeShift(_modelKey.fbeSize() + _modelValue.fbeSize());
        }

        return true;
    }

    // Get the map as java.util.TreeMap
    public void get(java.util.TreeMap<_KEY_TYPE_, _VALUE_TYPE_> values)
    {
        assert (values != null) : "Invalid values parameter!";
        if (values == null)
            throw new IllegalArgumentException("Invalid values parameter!");

        values.clear();

        long fbeMapSize = getSize();
        if (fbeMapSize == 0)
            return;

        var fbeModel = getItem(0);
        for (long i = fbeMapSize; i-- > 0;)
        {
            _KEY_TYPE_ key = fbeModel.getKey().get();
            _VALUE_TYPE_ value = fbeModel.getValue().get();
            values.put(key, value);
            fbeModel.getKey().fbeShift(fbeModel.getKey().fbeSize() + fbeModel.getValue().fbeSize());
            fbeModel.getValue().fbeShift(fbeModel.getKey().fbeSize() + fbeModel.getValue().fbeSize());
        }
    }

    // Get the map as java.util.HashMap
    public void get(java.util.HashMap<_KEY_TYPE_, _VALUE_TYPE_> values)
    {
        assert (values != null) : "Invalid values parameter!";
        if (values == null)
            throw new IllegalArgumentException("Invalid values parameter!");

        values.clear();

        long fbeMapSize = getSize();
        if (fbeMapSize == 0)
            return;

        var fbeModel = getItem(0);
        for (long i = fbeMapSize; i-- > 0;)
        {
            _KEY_TYPE_ key = fbeModel.getKey().get();
            _VALUE_TYPE_ value = fbeModel.getValue().get();
            values.put(key, value);
            fbeModel.getKey().fbeShift(fbeModel.getKey().fbeSize() + fbeModel.getValue().fbeSize());
            fbeModel.getValue().fbeShift(fbeModel.getKey().fbeSize() + fbeModel.getValue().fbeSize());
        }
    }

    // Set the map as java.util.TreeMap
    public void set(java.util.TreeMap<_KEY_TYPE_, _VALUE_TYPE_> values)
    {
        assert (values != null) : "Invalid values parameter!";
        if (values == null)
            throw new IllegalArgumentException("Invalid values parameter!");

        assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return;

        var fbeModel = resize(values.size());
        for (var value : values.entrySet())
        {
            fbeModel.getKey().set(value.getKey());
            fbeModel.getKey().fbeShift(fbeModel.getKey().fbeSize() + fbeModel.getValue().fbeSize());
            fbeModel.getValue().set(value.getValue());
            fbeModel.getValue().fbeShift(fbeModel.getKey().fbeSize() + fbeModel.getValue().fbeSize());
        }
    }

    // Set the map as java.util.HashMap
    public void set(java.util.HashMap<_KEY_TYPE_, _VALUE_TYPE_> values)
    {
        assert (values != null) : "Invalid values parameter!";
        if (values == null)
            throw new IllegalArgumentException("Invalid values parameter!");

        assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return;

        var fbeModel = resize(values.size());
        for (var value : values.entrySet())
        {
            fbeModel.getKey().set(value.getKey());
            fbeModel.getKey().fbeShift(fbeModel.getKey().fbeSize() + fbeModel.getValue().fbeSize());
            fbeModel.getValue().set(value.getValue());
            fbeModel.getValue().fbeShift(fbeModel.getKey().fbeSize() + fbeModel.getValue().fbeSize());
        }
    }
}
)CODE";

    std::string key_type_name = IsPackageType(key_type) ? key_type : (domain + package + "." + key_type);
    std::string value_type_name = IsPackageType(value_type) ? value_type : (domain + package + "." + value_type);

    // Prepare code template
    code = std::regex_replace(code, std::regex("_DOMAIN_"), domain);
    code = std::regex_replace(code, std::regex("_KEY_NAME_"), key_name);
    code = std::regex_replace(code, std::regex("_KEY_TYPE_"), key_type_name);
    code = std::regex_replace(code, std::regex("_KEY_MODEL_"), key_model);
    code = std::regex_replace(code, std::regex("_VALUE_NAME_"), value_name);
    code = std::regex_replace(code, std::regex("_VALUE_TYPE_"), value_type_name);
    code = std::regex_replace(code, std::regex("_VALUE_MODEL_"), value_model);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateFBEFieldModelEnumFlags(const std::string& domain, const std::string& package, const std::string& name, const std::string& type)
{
    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the file
    CppCommon::Path file = path / ("FieldModel" + name + ".java");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    std::string code = R"CODE(
// Fast Binary Encoding _NAME_ field model
public final class FieldModel_NAME_ extends _DOMAIN_fbe.FieldModel
{
    public FieldModel_NAME_(_DOMAIN_fbe.Buffer buffer, long offset) { super(buffer, offset); }

    // Get the field size
    @Override
    public long fbeSize() { return _SIZE_; }

    // Get the value
    public _DOMAIN__PACKAGE_._NAME_ get() { return get(new _DOMAIN__PACKAGE_._NAME_()); }
    public _DOMAIN__PACKAGE_._NAME_ get(_DOMAIN__PACKAGE_._NAME_ defaults)
    {
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return defaults;

        return new _DOMAIN__PACKAGE_._NAME_(_READ_(fbeOffset()));
    }

    // Set the value
    public void set(_DOMAIN__PACKAGE_._NAME_ value)
    {
        assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return;

        write(fbeOffset(), value.getRaw());
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_DOMAIN_"), domain);
    code = std::regex_replace(code, std::regex("_PACKAGE_"), package);
    code = std::regex_replace(code, std::regex("_NAME_"), name);
    code = std::regex_replace(code, std::regex("_SIZE_"), ConvertEnumSize(type));
    code = std::regex_replace(code, std::regex("_READ_"), ConvertEnumRead(type));
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateFBESize(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "Size.java";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding size
public class Size
{
    public long value;

    // Initialize a new size
    public Size() { value = 0; }
    public Size(long size) { value = size; }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateFBEFinalModel(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "FinalModel.java";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding base final model
public abstract class FinalModel
{
    protected Buffer _buffer;
    protected long _offset;

    // Get the final offset
    public long fbeOffset() { return _offset; }
    // Set the final offset
    public void fbeOffset(long value) { _offset = value; }

    // Get the final size
    public long fbeSize() { return 0; }
    // Get the final extra size
    public long fbeExtra() { return 0; }

    // Shift the current final offset
    public void fbeShift(long size) { _offset += size; }
    // Unshift the current final offset
    public void fbeUnshift(long size) { _offset -= size; }

    // Initialize a new final model
    protected FinalModel(Buffer buffer, long offset)
    {
        _buffer = buffer;
        _offset = offset;
    }

    // Check if the value is valid
    public abstract long verify();

    // Buffer I/O methods
    protected boolean readBoolean(long offset) { return Buffer.readBoolean(_buffer.getData(), _buffer.getOffset() + offset); }
    protected byte readByte(long offset) { return Buffer.readByte(_buffer.getData(), _buffer.getOffset() + offset); }
    protected char readChar(long offset) { return Buffer.readChar(_buffer.getData(), _buffer.getOffset() + offset); }
    protected char readWChar(long offset) { return Buffer.readWChar(_buffer.getData(), _buffer.getOffset() + offset); }
    protected byte readInt8(long offset) { return Buffer.readInt8(_buffer.getData(), _buffer.getOffset() + offset); }
    protected short readInt16(long offset) { return Buffer.readInt16(_buffer.getData(), _buffer.getOffset() + offset); }
    protected int readInt32(long offset) { return Buffer.readInt32(_buffer.getData(), _buffer.getOffset() + offset); }
    protected long readInt64(long offset) { return Buffer.readInt64(_buffer.getData(), _buffer.getOffset() + offset); }
    protected float readFloat(long offset) { return Buffer.readFloat(_buffer.getData(), _buffer.getOffset() + offset); }
    protected double readDouble(long offset) { return Buffer.readDouble(_buffer.getData(), _buffer.getOffset() + offset); }
    protected byte[] readBytes(long offset, long size) { return Buffer.readBytes(_buffer.getData(), _buffer.getOffset() + offset, size); }
    protected String readString(long offset, long size) { return Buffer.readString(_buffer.getData(), _buffer.getOffset() + offset, size); }
    protected java.util.UUID readUUID(long offset) { return Buffer.readUUID(_buffer.getData(), _buffer.getOffset() + offset); }
    protected void write(long offset, boolean value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
    protected void write(long offset, byte value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
    protected void write(long offset, short value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
    protected void write(long offset, int value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
    protected void write(long offset, long value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
    protected void write(long offset, float value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
    protected void write(long offset, double value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
    protected void write(long offset, byte[] value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
    protected void write(long offset, byte[] value, long valueOffset, long valueSize) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value, valueOffset, valueSize); }
    protected void write(long offset, byte value, long valueCount) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value, valueCount); }
    protected void write(long offset, java.util.UUID value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateFBEFinalModel(const std::string& domain, const std::string& package, const std::string& name, const std::string& type, const std::string& base, const std::string& size, const std::string& defaults)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / ("FinalModel" + name + ".java");
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding _TYPE_ final model
public final class FinalModel_NAME_ extends FinalModel
{
    public FinalModel_NAME_(Buffer buffer, long offset) { super(buffer, offset); }

    // Get the allocation size
    public long fbeAllocationSize(_TYPE_ value) { return fbeSize(); }

    // Get the final size
    @Override
    public long fbeSize() { return _SIZE_; }

    // Check if the value is valid
    @Override
    public long verify()
    {
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return Long.MAX_VALUE;

        return fbeSize();
    }

    // Get the value
    public _TYPE_ get(Size size)
    {
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return _DEFAULTS_;

        size.value = fbeSize();
        return read_NAME_(fbeOffset());
    }

    // Set the value
    public long set(_TYPE_ value)
    {
        assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return 0;

        write(fbeOffset(), _BASE_value);
        return fbeSize();
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_NAME_"), name);
    code = std::regex_replace(code, std::regex("_TYPE_"), type);
    code = std::regex_replace(code, std::regex("_BASE_"), base);
    code = std::regex_replace(code, std::regex("_SIZE_"), size);
    code = std::regex_replace(code, std::regex("_DEFAULTS_"), defaults);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateFBEFinalModelDecimal(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "FinalModelDecimal.java";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding decimal final model
public final class FinalModelDecimal extends FinalModel
{
    public FinalModelDecimal(Buffer buffer, long offset) { super(buffer, offset); }

    // Get the allocation size
    public long fbeAllocationSize(java.math.BigDecimal value) { return fbeSize(); }

    // Get the final size
    @Override
    public long fbeSize() { return 16; }

    // Check if the decimal value is valid
    @Override
    public long verify()
    {
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return Long.MAX_VALUE;

        return fbeSize();
    }

    // Get the decimal value
    public java.math.BigDecimal get(Size size)
    {
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return java.math.BigDecimal.valueOf(0L);

        byte[] magnitude = readBytes(fbeOffset(), 12);
        int scale = readByte(fbeOffset() + 14);
        int signum = (readByte(fbeOffset() + 15) < 0) ? -1 : 1;

        // Reverse magnitude
        for(int i = 0; i < magnitude.length / 2; i++)
        {
            byte temp = magnitude[i];
            magnitude[i] = magnitude[magnitude.length - i - 1];
            magnitude[magnitude.length - i - 1] = temp;
        }

        var unscaled = new java.math.BigInteger(signum, magnitude);

        size.value = fbeSize();
        return new java.math.BigDecimal(unscaled, scale);
    }

    // Set the decimal value
    public long set(java.math.BigDecimal value)
    {
        assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return 0;

        // Get unscaled absolute value
        java.math.BigInteger unscaled = value.abs().unscaledValue();
        int bitLength = unscaled.bitLength();
        if ((bitLength < 0) || (bitLength > 96))
        {
            // Value too big for .NET Decimal (bit length is limited to [0, 96])
            write(fbeOffset(), (byte)0, fbeSize());
            return fbeSize();
        }

        // Get byte array
        byte[] unscaledBytes = unscaled.toByteArray();

        // Get scale
        int scale = value.scale();
        if ((scale < 0) || (scale > 28))
        {
            // Value scale exceeds .NET Decimal limit of [0, 28]
            write(fbeOffset(), (byte)0, fbeSize());
            return fbeSize();
        }

        // Write unscaled value to bytes 0-11
        int index = 0;
        for (int i = unscaledBytes.length - 1; (i >= 0) && (index < 12); i--, index++)
            write(fbeOffset() + index, unscaledBytes[i]);

        // Fill remaining bytes with zeros
        for (; index < 14; index++)
            write(fbeOffset() + index, (byte)0);

        // Write scale at byte 14
        write(fbeOffset() + 14, (byte)scale);

        // Write signum at byte 15
        write(fbeOffset() + 15, (byte)((value.signum() < 0) ? -128 : 0));
        return fbeSize();
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateFBEFinalModelDate(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "FinalModelDate.java";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding date final model
public final class FinalModelDate extends FinalModel
{
    public FinalModelDate(Buffer buffer, long offset) { super(buffer, offset); }

    // Get the allocation size
    public long fbeAllocationSize(java.util.Date value) { return fbeSize(); }

    // Get the final size
    @Override
    public long fbeSize() { return 8; }

    // Check if the date value is valid
    @Override
    public long verify()
    {
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return Long.MAX_VALUE;

        return fbeSize();
    }

    // Get the date value
    public java.util.Date get(Size size)
    {
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return new java.util.Date(0);

        size.value = fbeSize();
        long nanoseconds = readInt64(fbeOffset());
        return new java.util.Date(nanoseconds / 1000000);
    }

    // Set the date value
    public long set(java.util.Date value)
    {
        assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return 0;

        long nanoseconds = value.getTime() * 1000;
        write(fbeOffset(), nanoseconds);
        return fbeSize();
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateFBEFinalModelTimestamp(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "FinalModelTimestamp.java";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding timestamp final model
public final class FinalModelTimestamp extends FinalModel
{
    public FinalModelTimestamp(Buffer buffer, long offset) { super(buffer, offset); }

    // Get the allocation size
    public long fbeAllocationSize(java.time.Instant value) { return fbeSize(); }

    // Get the final size
    @Override
    public long fbeSize() { return 8; }

    // Check if the timestamp value is valid
    @Override
    public long verify()
    {
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return Long.MAX_VALUE;

        return fbeSize();
    }

    // Get the timestamp value
    public java.time.Instant get(Size size)
    {
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return java.time.Instant.EPOCH;

        size.value = fbeSize();
        long nanoseconds = readInt64(fbeOffset());
        return java.time.Instant.ofEpochSecond(nanoseconds / 1000000000, nanoseconds % 1000000000);
    }

    // Set the timestamp value
    public long set(java.time.Instant value)
    {
        assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return 0;

        long nanoseconds = value.getEpochSecond() * 1000000000 + value.getNano();
        write(fbeOffset(), nanoseconds);
        return fbeSize();
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateFBEFinalModelBytes(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "FinalModelBytes.java";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding bytes final model
public final class FinalModelBytes extends FinalModel
{
    public FinalModelBytes(Buffer buffer, long offset) { super(buffer, offset); }

    // Get the allocation size
    public long fbeAllocationSize(java.nio.ByteBuffer value) { return 4 + value.array().length; }

    // Check if the bytes value is valid
    @Override
    public long verify()
    {
        if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
            return Long.MAX_VALUE;

        int fbeBytesSize = readInt32(fbeOffset());
        if ((_buffer.getOffset() + fbeOffset() + 4 + fbeBytesSize) > _buffer.getSize())
            return Long.MAX_VALUE;

        return 4 + fbeBytesSize;
    }

    // Get the bytes value
    public java.nio.ByteBuffer get(Size size)
    {
        if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
        {
            size.value = 0;
            return java.nio.ByteBuffer.allocate(0);
        }

        int fbeBytesSize = readInt32(fbeOffset());
        assert ((_buffer.getOffset() + fbeOffset() + 4 + fbeBytesSize) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + 4 + fbeBytesSize) > _buffer.getSize())
        {
            size.value = 4;
            return java.nio.ByteBuffer.allocate(0);
        }

        size.value = 4 + fbeBytesSize;
        return java.nio.ByteBuffer.wrap(readBytes(fbeOffset() + 4, fbeBytesSize));
    }

    // Set the bytes value
    public long set(java.nio.ByteBuffer value)
    {
        assert (value != null) : "Invalid bytes value!";
        if (value == null)
            throw new IllegalArgumentException("Invalid bytes value!");

        assert ((_buffer.getOffset() + fbeOffset() + 4) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
            return 0;

        int fbeBytesSize = value.array().length;
        assert ((_buffer.getOffset() + fbeOffset() + 4 + fbeBytesSize) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + 4 + fbeBytesSize) > _buffer.getSize())
            return 4;

        write(fbeOffset(), fbeBytesSize);
        write(fbeOffset() + 4, value.array());
        return 4 + fbeBytesSize;
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateFBEFinalModelString(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "FinalModelString.java";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding string final model
public final class FinalModelString extends FinalModel
{
    public FinalModelString(Buffer buffer, long offset) { super(buffer, offset); }

    // Get the allocation size
    public long fbeAllocationSize(String value) { return 4 + 3 * (value.length() + 1); }

    // Check if the string value is valid
    @Override
    public long verify()
    {
        if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
            return Long.MAX_VALUE;

        int fbeStringSize = readInt32(fbeOffset());
        if ((_buffer.getOffset() + fbeOffset() + 4 + fbeStringSize) > _buffer.getSize())
            return Long.MAX_VALUE;

        return 4 + fbeStringSize;
    }

    // Get the string value
    public String get(Size size)
    {
        if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
        {
            size.value = 0;
            return "";
        }

        int fbeStringSize = readInt32(fbeOffset());
        assert ((_buffer.getOffset() + fbeOffset() + 4 + fbeStringSize) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + 4 + fbeStringSize) > _buffer.getSize())
        {
            size.value = 4;
            return "";
        }

        size.value = 4 + fbeStringSize;
        return readString(fbeOffset() + 4, fbeStringSize);
    }

    // Set the string value
    public long set(String value)
    {
        assert (value != null) : "Invalid string value!";
        if (value == null)
            throw new IllegalArgumentException("Invalid string value!");

        assert ((_buffer.getOffset() + fbeOffset() + 4) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
            return 0;

        byte[] bytes = value.getBytes(java.nio.charset.StandardCharsets.UTF_8);

        int fbeStringSize = bytes.length;
        assert ((_buffer.getOffset() + fbeOffset() + 4 + fbeStringSize) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + 4 + fbeStringSize) > _buffer.getSize())
            return 4;

        write(fbeOffset(), fbeStringSize);
        write(fbeOffset() + 4, bytes);
        return 4 + fbeStringSize;
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateFBEFinalModelOptional(const std::string& domain, const std::string& package, const std::string& name, const std::string& type, const std::string& model)
{
    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the file
    CppCommon::Path file = path / ("FinalModelOptional" + name + ".java");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    std::string code = R"CODE(
// Fast Binary Encoding optional _NAME_ final model
public final class FinalModelOptional_NAME_ extends _DOMAIN_fbe.FinalModel
{
    public FinalModelOptional_NAME_(_DOMAIN_fbe.Buffer buffer, long offset)
    {
        super(buffer, offset);
        value = new _MODEL_(buffer, 0);
    }

    // Get the allocation size
    public long fbeAllocationSize(_TYPE_ optional) { return 1 + ((optional != null) ? value.fbeAllocationSize(optional) : 0); }

    // Checks if the object contains a value
    public boolean hasValue()
    {
        if ((_buffer.getOffset() + fbeOffset() + 1) > _buffer.getSize())
            return false;

        byte fbeHasValue = readInt8(fbeOffset());
        return (fbeHasValue != 0);
    }

    // Base final model value
    public final _MODEL_ value;

    // Check if the optional value is valid
    @Override
    public long verify()
    {
        if ((_buffer.getOffset() + fbeOffset() + 1) > _buffer.getSize())
            return Long.MAX_VALUE;

        byte fbeHasValue = readInt8(fbeOffset());
        if (fbeHasValue == 0)
            return 1;

        _buffer.shift(fbeOffset() + 1);
        long fbeResult = value.verify();
        _buffer.unshift(fbeOffset() + 1);
        return 1 + fbeResult;
    }

    // Get the optional value
    public _TYPE_ get(_DOMAIN_fbe.Size size)
    {
        assert ((_buffer.getOffset() + fbeOffset() + 1) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + 1) > _buffer.getSize())
        {
            size.value = 0;
            return null;
        }

        if (!hasValue())
        {
            size.value = 1;
            return null;
        }

        _buffer.shift(fbeOffset() + 1);
        _TYPE_ optional = value.get(size);
        _buffer.unshift(fbeOffset() + 1);
        size.value += 1;
        return optional;
    }

    // Set the optional value
    public long set(_TYPE_ optional)
    {
        assert ((_buffer.getOffset() + fbeOffset() + 1) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + 1) > _buffer.getSize())
            return 0;

        byte fbeHasValue = (byte)((optional != null) ? 1 : 0);
        write(fbeOffset(), fbeHasValue);
        if (fbeHasValue == 0)
            return 1;

        _buffer.shift(fbeOffset() + 1);
        long size = value.set(optional);
        _buffer.unshift(fbeOffset() + 1);
        return 1 + size;
    }
}
)CODE";

    std::string type_name = IsPackageType(type) ? type : (domain + package + "." + type);

    // Prepare code template
    code = std::regex_replace(code, std::regex("_DOMAIN_"), domain);
    code = std::regex_replace(code, std::regex("_NAME_"), name);
    code = std::regex_replace(code, std::regex("_TYPE_"), type_name);
    code = std::regex_replace(code, std::regex("_MODEL_"), model);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateFBEFinalModelArray(const std::string& domain, const std::string& package, const std::string& name, const std::string& type, const std::string& model, bool bytes)
{
    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the file
    CppCommon::Path file = path / ("FinalModelArray" + name + ".java");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    std::string code = R"CODE(
// Fast Binary Encoding _NAME_ array final model
public final class FinalModelArray_NAME_ extends _DOMAIN_fbe.FinalModel
{
    private final _MODEL_ _model;
    private final long _size;

    public FinalModelArray_NAME_(_DOMAIN_fbe.Buffer buffer, long offset, long size)
    {
        super(buffer, offset);
        _model = new _MODEL_(buffer, offset);
        _size = size;
    }

    // Get the allocation size
    public long fbeAllocationSize(_ARRAY_ values)
    {
        long size = 0;
        for (long i = 0; (i < values.length) && (i < _size); i++)
            size += _model.fbeAllocationSize(values[(int)i]);
        return size;
    }
    public long fbeAllocationSize(java.util.ArrayList<_TYPE_> values)
    {
        long size = 0;
        for (long i = 0; (i < values.size()) && (i < _size); i++)
            size += _model.fbeAllocationSize(values.get((int)i));
        return size;
    }

    // Check if the array is valid
    @Override
    public long verify()
    {
        if ((_buffer.getOffset() + fbeOffset()) > _buffer.getSize())
            return Long.MAX_VALUE;

        long size = 0;
        _model.fbeOffset(fbeOffset());
        for (long i = _size; i-- > 0;)
        {
            long offset = _model.verify();
            if (offset == Long.MAX_VALUE)
                return Long.MAX_VALUE;
            _model.fbeShift(offset);
            size += offset;
        }
        return size;
    }

    // Get the array
    public _ARRAY_ get(_DOMAIN_fbe.Size size)
    {
        var values = _INIT_;

        assert ((_buffer.getOffset() + fbeOffset()) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset()) > _buffer.getSize())
        {
            size.value = 0;
            return values;
        }

        size.value = 0;
        var offset = new _DOMAIN_fbe.Size();
        _model.fbeOffset(fbeOffset());
        for (long i = 0; i < _size; i++)
        {
            offset.value = 0;
            values[(int)i] = _model.get(offset);
            _model.fbeShift(offset.value);
            size.value += offset.value;
        }
        return values;
    }

    // Get the array
    public long get(_ARRAY_ values)
    {
        assert (values != null) : "Invalid values parameter!";
        if (values == null)
            throw new IllegalArgumentException("Invalid values parameter!");

        assert ((_buffer.getOffset() + fbeOffset()) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset()) > _buffer.getSize())
            return 0;

        long size = 0;
        var offset = new _DOMAIN_fbe.Size();
        _model.fbeOffset(fbeOffset());
        for (long i = 0; (i < values.length) && (i < _size); i++)
        {
            offset.value = 0;
            values[(int)i] = _model.get(offset);
            _model.fbeShift(offset.value);
            size += offset.value;
        }
        return size;
    }

    // Get the array as java.util.ArrayList
    public long get(java.util.ArrayList<_TYPE_> values)
    {
        assert (values != null) : "Invalid values parameter!";
        if (values == null)
            throw new IllegalArgumentException("Invalid values parameter!");

        values.clear();

        assert ((_buffer.getOffset() + fbeOffset()) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset()) > _buffer.getSize())
            return 0;

        values.ensureCapacity((int)_size);

        long size = 0;
        var offset = new _DOMAIN_fbe.Size();
        _model.fbeOffset(fbeOffset());
        for (long i = _size; i-- > 0;)
        {
            offset.value = 0;
            _TYPE_ value = _model.get(offset);
            values.add(value);
            _model.fbeShift(offset.value);
            size += offset.value;
        }
        return size;
    }

    // Set the array
    public long set(_ARRAY_ values)
    {
        assert (values != null) : "Invalid values parameter!";
        if (values == null)
            throw new IllegalArgumentException("Invalid values parameter!");

        assert ((_buffer.getOffset() + fbeOffset()) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset()) > _buffer.getSize())
            return 0;

        long size = 0;
        _model.fbeOffset(fbeOffset());
        for (long i = 0; (i < values.length) && (i < _size); i++)
        {
            long offset = _model.set(values[(int)i]);
            _model.fbeShift(offset);
            size += offset;
        }
        return size;
    }

    // Set the array as java.util.ArrayList
    public long set(java.util.ArrayList<_TYPE_> values)
    {
        assert (values != null) : "Invalid values parameter!";
        if (values == null)
            throw new IllegalArgumentException("Invalid values parameter!");

        assert ((_buffer.getOffset() + fbeOffset()) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset()) > _buffer.getSize())
            return 0;

        long size = 0;
        _model.fbeOffset(fbeOffset());
        for (long i = 0; (i < values.size()) && (i < _size); i++)
        {
            long offset = _model.set(values.get((int)i));
            _model.fbeShift(offset);
            size += offset;
        }
        return size;
    }
}
)CODE";

    std::string type_name = IsPackageType(type) ? type : (domain + package + "." + type);

    // Prepare code template
    code = std::regex_replace(code, std::regex("_DOMAIN_"), domain);
    code = std::regex_replace(code, std::regex("_NAME_"), name);
    code = std::regex_replace(code, std::regex("_TYPE_"), type_name);
    code = std::regex_replace(code, std::regex("_MODEL_"), model);
    code = std::regex_replace(code, std::regex("_ARRAY_"), (bytes ? "byte" : type_name) + "[]");
    code = std::regex_replace(code, std::regex("_INIT_"), ((type != "byte[]") ? ("new " + (bytes ? "byte" : type_name) + "[(int)_size]") : "new byte[(int)_size][]"));
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateFBEFinalModelVector(const std::string& domain, const std::string& package, const std::string& name, const std::string& type, const std::string& model)
{
    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the file
    CppCommon::Path file = path / ("FinalModelVector" + name + ".java");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    std::string code = R"CODE(
// Fast Binary Encoding _NAME_ vector final model
public final class FinalModelVector_NAME_ extends _DOMAIN_fbe.FinalModel
{
    private final _MODEL_ _model;

    public FinalModelVector_NAME_(_DOMAIN_fbe.Buffer buffer, long offset)
    {
        super(buffer, offset);
        _model = new _MODEL_(buffer, offset);
    }

    // Get the allocation size
    public long fbeAllocationSize(_TYPE_[] values)
    {
        long size = 4;
        for (var value : values)
            size += _model.fbeAllocationSize(value);
        return size;
    }
    public long fbeAllocationSize(java.util.ArrayList<_TYPE_> values)
    {
        long size = 4;
        for (var value : values)
            size += _model.fbeAllocationSize(value);
        return size;
    }
    public long fbeAllocationSize(java.util.LinkedList<_TYPE_> values)
    {
        long size = 4;
        for (var value : values)
            size += _model.fbeAllocationSize(value);
        return size;
    }
    public long fbeAllocationSize(java.util.HashSet<_TYPE_> values)
    {
        long size = 4;
        for (var value : values)
            size += _model.fbeAllocationSize(value);
        return size;
    }

    // Check if the vector is valid
    @Override
    public long verify()
    {
        if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
            return Long.MAX_VALUE;

        int fbeVectorSize = readInt32(fbeOffset());

        long size = 4;
        _model.fbeOffset(fbeOffset() + 4);
        for (int i = fbeVectorSize; i-- > 0;)
        {
            long offset = _model.verify();
            if (offset == Long.MAX_VALUE)
                return Long.MAX_VALUE;
            _model.fbeShift(offset);
            size += offset;
        }
        return size;
    }

    // Get the vector as java.util.ArrayList
    public long get(java.util.ArrayList<_TYPE_> values)
    {
        assert (values != null) : "Invalid values parameter!";
        if (values == null)
            throw new IllegalArgumentException("Invalid values parameter!");

        values.clear();

        assert ((_buffer.getOffset() + fbeOffset() + 4) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
            return 0;

        long fbeVectorSize = readInt32(fbeOffset());
        if (fbeVectorSize == 0)
            return 4;

        values.ensureCapacity((int)fbeVectorSize);

        long size = 4;
        var offset = new _DOMAIN_fbe.Size();
        _model.fbeOffset(fbeOffset() + 4);
        for (long i = 0; i < fbeVectorSize; i++)
        {
            offset.value = 0;
            _TYPE_ value = _model.get(offset);
            values.add(value);
            _model.fbeShift(offset.value);
            size += offset.value;
        }
        return size;
    }

    // Get the vector as java.util.LinkedList
    public long get(java.util.LinkedList<_TYPE_> values)
    {
        assert (values != null) : "Invalid values parameter!";
        if (values == null)
            throw new IllegalArgumentException("Invalid values parameter!");

        values.clear();

        assert ((_buffer.getOffset() + fbeOffset() + 4) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
            return 0;

        long fbeVectorSize = readInt32(fbeOffset());
        if (fbeVectorSize == 0)
            return 4;

        long size = 4;
        var offset = new _DOMAIN_fbe.Size();
        _model.fbeOffset(fbeOffset() + 4);
        for (long i = 0; i < fbeVectorSize; i++)
        {
            offset.value = 0;
            _TYPE_ value = _model.get(offset);
            values.add(value);
            _model.fbeShift(offset.value);
            size += offset.value;
        }
        return size;
    }

    // Get the vector as java.util.HashSet
    public long get(java.util.HashSet<_TYPE_> values)
    {
        assert (values != null) : "Invalid values parameter!";
        if (values == null)
            throw new IllegalArgumentException("Invalid values parameter!");

        values.clear();

        assert ((_buffer.getOffset() + fbeOffset() + 4) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
            return 0;

        long fbeVectorSize = readInt32(fbeOffset());
        if (fbeVectorSize == 0)
            return 4;

        long size = 4;
        var offset = new _DOMAIN_fbe.Size();
        _model.fbeOffset(fbeOffset() + 4);
        for (long i = 0; i < fbeVectorSize; i++)
        {
            offset.value = 0;
            _TYPE_ value = _model.get(offset);
            values.add(value);
            _model.fbeShift(offset.value);
            size += offset.value;
        }
        return size;
    }

    // Set the vector as java.util.ArrayList
    public long set(java.util.ArrayList<_TYPE_> values)
    {
        assert (values != null) : "Invalid values parameter!";
        if (values == null)
            throw new IllegalArgumentException("Invalid values parameter!");

        assert ((_buffer.getOffset() + fbeOffset() + 4) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
            return 0;

        write(fbeOffset(), (int)values.size());

        long size = 4;
        _model.fbeOffset(fbeOffset() + 4);
        for (var value : values)
        {
            long offset = _model.set(value);
            _model.fbeShift(offset);
            size += offset;
        }
        return size;
    }

    // Set the vector as java.util.LinkedList
    public long set(java.util.LinkedList<_TYPE_> values)
    {
        assert (values != null) : "Invalid values parameter!";
        if (values == null)
            throw new IllegalArgumentException("Invalid values parameter!");

        assert ((_buffer.getOffset() + fbeOffset() + 4) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
            return 0;

        write(fbeOffset(), (int)values.size());

        long size = 4;
        _model.fbeOffset(fbeOffset() + 4);
        for (var value : values)
        {
            long offset = _model.set(value);
            _model.fbeShift(offset);
            size += offset;
        }
        return size;
    }

    // Set the vector as java.util.HashSet
    public long set(java.util.HashSet<_TYPE_> values)
    {
        assert (values != null) : "Invalid values parameter!";
        if (values == null)
            throw new IllegalArgumentException("Invalid values parameter!");

        assert ((_buffer.getOffset() + fbeOffset() + 4) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
            return 0;

        write(fbeOffset(), (int)values.size());

        long size = 4;
        _model.fbeOffset(fbeOffset() + 4);
        for (var value : values)
        {
            long offset = _model.set(value);
            _model.fbeShift(offset);
            size += offset;
        }
        return size;
    }
}
)CODE";

    std::string type_name = IsPackageType(type) ? type : (domain + package + "." + type);

    // Prepare code template
    code = std::regex_replace(code, std::regex("_DOMAIN_"), domain);
    code = std::regex_replace(code, std::regex("_NAME_"), name);
    code = std::regex_replace(code, std::regex("_TYPE_"), type_name);
    code = std::regex_replace(code, std::regex("_MODEL_"), model);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateFBEFinalModelMap(const std::string& domain, const std::string& package, const std::string& key_name, const std::string& key_type, const std::string& key_model, const std::string& value_name, const std::string& value_type, const std::string& value_model)
{
    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the file
    CppCommon::Path file = path / ("FinalModelMap" + key_name + value_name + ".java");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    std::string code = R"CODE(
// Fast Binary Encoding _KEY_NAME_->_VALUE_NAME_ map final model
public final class FinalModelMap_KEY_NAME__VALUE_NAME_ extends _DOMAIN_fbe.FinalModel
{
    private final _KEY_MODEL_ _modelKey;
    private final _VALUE_MODEL_ _modelValue;

    public FinalModelMap_KEY_NAME__VALUE_NAME_(_DOMAIN_fbe.Buffer buffer, long offset)
    {
        super(buffer, offset);
        _modelKey = new _KEY_MODEL_(buffer, offset);
        _modelValue = new _VALUE_MODEL_(buffer, offset);
    }

    // Get the allocation size
    public long fbeAllocationSize(java.util.TreeMap<_KEY_TYPE_, _VALUE_TYPE_> values)
    {
        long size = 4;
        for (var value : values.entrySet())
        {
            size += _modelKey.fbeAllocationSize(value.getKey());
            size += _modelValue.fbeAllocationSize(value.getValue());
        }
        return size;
    }
    public long fbeAllocationSize(java.util.HashMap<_KEY_TYPE_, _VALUE_TYPE_> values)
    {
        long size = 4;
        for (var value : values.entrySet())
        {
            size += _modelKey.fbeAllocationSize(value.getKey());
            size += _modelValue.fbeAllocationSize(value.getValue());
        }
        return size;
    }

    // Check if the map is valid
    @Override
    public long verify()
    {
        if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
            return Long.MAX_VALUE;

        int fbeMapSize = readInt32(fbeOffset());

        long size = 4;
        _modelKey.fbeOffset(fbeOffset() + 4);
        _modelValue.fbeOffset(fbeOffset() + 4);
        for (int i = fbeMapSize; i-- > 0;)
        {
            long offsetKey = _modelKey.verify();
            if (offsetKey == Long.MAX_VALUE)
                return Long.MAX_VALUE;
            _modelKey.fbeShift(offsetKey);
            _modelValue.fbeShift(offsetKey);
            size += offsetKey;
            long offsetValue = _modelValue.verify();
            if (offsetValue == Long.MAX_VALUE)
                return Long.MAX_VALUE;
            _modelKey.fbeShift(offsetValue);
            _modelValue.fbeShift(offsetValue);
            size += offsetValue;
        }
        return size;
    }

    // Get the map as java.util.TreeMap
    public long get(java.util.TreeMap<_KEY_TYPE_, _VALUE_TYPE_> values)
    {
        assert (values != null) : "Invalid values parameter!";
        if (values == null)
            throw new IllegalArgumentException("Invalid values parameter!");

        values.clear();

        assert ((_buffer.getOffset() + fbeOffset() + 4) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
            return 0;

        long fbeMapSize = readInt32(fbeOffset());
        if (fbeMapSize == 0)
            return 4;

        long size = 4;
        var offset = new _DOMAIN_fbe.Size();
        _modelKey.fbeOffset(fbeOffset() + 4);
        _modelValue.fbeOffset(fbeOffset() + 4);
        for (long i = fbeMapSize; i-- > 0;)
        {
            offset.value = 0;
            _KEY_TYPE_ key = _modelKey.get(offset);
            _modelKey.fbeShift(offset.value);
            _modelValue.fbeShift(offset.value);
            size += offset.value;
            offset.value = 0;
            _VALUE_TYPE_ value = _modelValue.get(offset);
            _modelKey.fbeShift(offset.value);
            _modelValue.fbeShift(offset.value);
            size += offset.value;
            values.put(key, value);
        }
        return size;
    }

    // Get the map as java.util.HashMap
    public long get(java.util.HashMap<_KEY_TYPE_, _VALUE_TYPE_> values)
    {
        assert (values != null) : "Invalid values parameter!";
        if (values == null)
            throw new IllegalArgumentException("Invalid values parameter!");

        values.clear();

        assert ((_buffer.getOffset() + fbeOffset() + 4) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
            return 0;

        long fbeMapSize = readInt32(fbeOffset());
        if (fbeMapSize == 0)
            return 4;

        long size = 4;
        var offset = new _DOMAIN_fbe.Size();
        _modelKey.fbeOffset(fbeOffset() + 4);
        _modelValue.fbeOffset(fbeOffset() + 4);
        for (long i = fbeMapSize; i-- > 0;)
        {
            offset.value = 0;
            _KEY_TYPE_ key = _modelKey.get(offset);
            _modelKey.fbeShift(offset.value);
            _modelValue.fbeShift(offset.value);
            size += offset.value;
            offset.value = 0;
            _VALUE_TYPE_ value = _modelValue.get(offset);
            _modelKey.fbeShift(offset.value);
            _modelValue.fbeShift(offset.value);
            size += offset.value;

            values.put(key, value);
        }
        return size;
    }

    // Set the map as java.util.TreeMap
    public long set(java.util.TreeMap<_KEY_TYPE_, _VALUE_TYPE_> values)
    {
        assert (values != null) : "Invalid values parameter!";
        if (values == null)
            throw new IllegalArgumentException("Invalid values parameter!");

        assert ((_buffer.getOffset() + fbeOffset() + 4) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
            return 0;

        write(fbeOffset(), (int)values.size());

        long size = 4;
        _modelKey.fbeOffset(fbeOffset() + 4);
        _modelValue.fbeOffset(fbeOffset() + 4);
        for (var value : values.entrySet())
        {
            long offsetKey = _modelKey.set(value.getKey());
            _modelKey.fbeShift(offsetKey);
            _modelValue.fbeShift(offsetKey);
            long offsetValue = _modelValue.set(value.getValue());
            _modelKey.fbeShift(offsetValue);
            _modelValue.fbeShift(offsetValue);
            size += offsetKey + offsetValue;
        }
        return size;
    }

    // Set the map as java.util.HashMap
    public long set(java.util.HashMap<_KEY_TYPE_, _VALUE_TYPE_> values)
    {
        assert (values != null) : "Invalid values parameter!";
        if (values == null)
            throw new IllegalArgumentException("Invalid values parameter!");

        assert ((_buffer.getOffset() + fbeOffset() + 4) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
            return 0;

        write(fbeOffset(), (int)values.size());

        long size = 4;
        _modelKey.fbeOffset(fbeOffset() + 4);
        _modelValue.fbeOffset(fbeOffset() + 4);
        for (var value : values.entrySet())
        {
            long offsetKey = _modelKey.set(value.getKey());
            _modelKey.fbeShift(offsetKey);
            _modelValue.fbeShift(offsetKey);
            long offsetValue = _modelValue.set(value.getValue());
            _modelKey.fbeShift(offsetValue);
            _modelValue.fbeShift(offsetValue);
            size += offsetKey + offsetValue;
        }
        return size;
    }
}
)CODE";

    std::string key_type_name = IsPackageType(key_type) ? key_type : (domain + package + "." + key_type);
    std::string value_type_name = IsPackageType(value_type) ? value_type : (domain + package + "." + value_type);

    // Prepare code template
    code = std::regex_replace(code, std::regex("_DOMAIN_"), domain);
    code = std::regex_replace(code, std::regex("_KEY_NAME_"), key_name);
    code = std::regex_replace(code, std::regex("_KEY_TYPE_"), key_type_name);
    code = std::regex_replace(code, std::regex("_KEY_MODEL_"), key_model);
    code = std::regex_replace(code, std::regex("_VALUE_NAME_"), value_name);
    code = std::regex_replace(code, std::regex("_VALUE_TYPE_"), value_type_name);
    code = std::regex_replace(code, std::regex("_VALUE_MODEL_"), value_model);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateFBEFinalModelEnumFlags(const std::string& domain, const std::string& package, const std::string& name, const std::string& type)
{
    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the file
    CppCommon::Path file = path / ("FinalModel" + name + ".java");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    std::string code = R"CODE(
// Fast Binary Encoding _NAME_ final model
public final class FinalModel_NAME_ extends _DOMAIN_fbe.FinalModel
{
    public FinalModel_NAME_(_DOMAIN_fbe.Buffer buffer, long offset) { super(buffer, offset); }

    // Get the allocation size
    public long fbeAllocationSize(_DOMAIN__PACKAGE_._NAME_ value) { return fbeSize(); }

    // Get the final size
    @Override
    public long fbeSize() { return _SIZE_; }

    // Check if the value is valid
    @Override
    public long verify()
    {
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return Long.MAX_VALUE;

        return fbeSize();
    }

    // Get the value
    public _DOMAIN__PACKAGE_._NAME_ get(_DOMAIN_fbe.Size size)
    {
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return new _DOMAIN__PACKAGE_._NAME_();

        size.value = fbeSize();
        return new _DOMAIN__PACKAGE_._NAME_(_READ_(fbeOffset()));
    }

    // Set the value
    public long set(_DOMAIN__PACKAGE_._NAME_ value)
    {
        assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
        if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
            return 0;

        write(fbeOffset(), value.getRaw());
        return fbeSize();
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_DOMAIN_"), domain);
    code = std::regex_replace(code, std::regex("_PACKAGE_"), package);
    code = std::regex_replace(code, std::regex("_NAME_"), name);
    code = std::regex_replace(code, std::regex("_SIZE_"), ConvertEnumSize(type));
    code = std::regex_replace(code, std::regex("_READ_"), ConvertEnumRead(type));
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateFBESender(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "Sender.java";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding base sender
public abstract class Sender
{
    private Buffer _buffer;
    private boolean _logging;
    private boolean _final;

    // Get the bytes buffer
    public Buffer getBuffer() { return _buffer; }

    // Get the final protocol flag
    public boolean getFinal() { return _final; }

    // Get the logging flag
    public boolean getLogging() { return _logging; }
    // Enable/Disable logging
    public void setLogging(boolean enable) { _logging = enable; }

    protected Sender(boolean finalProto) { _buffer = new Buffer(); _final = finalProto; }
    protected Sender(Buffer buffer, boolean finalProto) { _buffer = buffer; _final = finalProto; }

    // Reset the sender buffer
    public void reset() { _buffer.reset(); }

    // Send serialized buffer.
    // Direct call of the method requires knowledge about internals of FBE models serialization.
    // Use it with care!
    public long sendSerialized(long serialized)
    {
        assert (serialized > 0) : "Invalid size of the serialized buffer!";
        if (serialized <= 0)
            return 0;

        // Shift the send buffer
        _buffer.shift(serialized);

        // Send the value
        long sent = onSend(_buffer.getData(), 0, _buffer.getSize());
        _buffer.remove(0, sent);
        return sent;
    }

    // Send message handler
    protected abstract long onSend(byte[] buffer, long offset, long size);
    // Send log message handler
    protected void onSendLog(String message) {}
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateFBEReceiver(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "Receiver.java";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding base receiver
public abstract class Receiver
{
    private Buffer _buffer;
    private boolean _logging;
    private boolean _final;

    // Get the bytes buffer
    public Buffer getBuffer() { return _buffer; }

    // Get the final protocol flag
    public boolean getFinal() { return _final; }

    // Get the logging flag
    public boolean getLogging() { return _logging; }
    // Enable/Disable logging
    public void setLogging(boolean enable) { _logging = enable; }

    protected Receiver(boolean finalProto) { _buffer = new Buffer(); _final = finalProto; }
    protected Receiver(Buffer buffer, boolean finalProto) { _buffer = buffer; _final = finalProto; }

    // Reset the receiver buffer
    public void reset() { _buffer.reset(); }

    // Receive data
    public void receive(Buffer buffer) { receive(buffer.getData(), 0, buffer.getSize()); }
    public void receive(byte[] buffer) { receive(buffer, 0, buffer.length); }
    public void receive(byte[] buffer, long offset, long size)
    {
        assert (buffer != null) : "Invalid buffer!";
        if (buffer == null)
            throw new IllegalArgumentException("Invalid buffer!");
        assert ((offset + size) <= buffer.length) : "Invalid offset & size!";
        if ((offset + size) > buffer.length)
            throw new IllegalArgumentException("Invalid offset & size!");

        if (size == 0)
            return;

        // Storage buffer
        long offset0 = _buffer.getOffset();
        long offset1 = _buffer.getSize();
        long size1 = _buffer.getSize();

        // Receive buffer
        long offset2 = 0;
        long size2 = size;

        // While receive buffer is available to handle...
        while (offset2 < size2)
        {
            byte[] messageBuffer = null;
            long messageOffset = 0;
            long messageSize = 0;

            // Try to receive message size
            boolean messageSizeCopied = false;
            boolean messageSizeFound = false;
            while (!messageSizeFound)
            {
                // Look into the storage buffer
                if (offset0 < size1)
                {
                    long count = Math.min(size1 - offset0, 4);
                    if (count == 4)
                    {
                        messageSizeCopied = true;
                        messageSizeFound = true;
                        messageSize = _buffer.readInt32(_buffer.getData(), offset0);
                        offset0 += 4;
                        break;
                    }
                    else
                    {
                        // Fill remaining data from the receive buffer
                        if (offset2 < size2)
                        {
                            count = Math.min(size2 - offset2, 4 - count);

                            // Allocate and refresh the storage buffer
                            _buffer.allocate(count);
                            size1 += count;

                            System.arraycopy(buffer, (int)(offset + offset2), _buffer.getData(), (int)offset1, (int)count);
                            offset1 += count;
                            offset2 += count;
                            continue;
                        }
                        else
                            break;
                    }
                }

                // Look into the receive buffer
                if (offset2 < size2)
                {
                    long count = Math.min(size2 - offset2, 4);
                    if (count == 4)
                    {
                        messageSizeFound = true;
                        messageSize = _buffer.readInt32(buffer, offset + offset2);
                        offset2 += 4;
                        break;
                    }
                    else
                    {
                        // Allocate and refresh the storage buffer
                        _buffer.allocate(count);
                        size1 += count;

                        System.arraycopy(buffer, (int)(offset + offset2), _buffer.getData(), (int)offset1, (int)count);
                        offset1 += count;
                        offset2 += count;
                        continue;
                    }
                }
                else
                    break;
            }

            if (!messageSizeFound)
                return;

            // Check the message full size
            long minSize = _final ? (4 + 4) : (4 + 4 + 4 + 4);
            assert (messageSize >= minSize) : "Invalid receive data!";
            if (messageSize < minSize)
                return;

            // Try to receive message body
            boolean messageFound = false;
            while (!messageFound)
            {
                // Look into the storage buffer
                if (offset0 < size1)
                {
                    long count = Math.min(size1 - offset0, messageSize - 4);
                    if (count == (messageSize - 4))
                    {
                        messageFound = true;
                        messageBuffer = _buffer.getData();
                        messageOffset = offset0 - 4;
                        offset0 += messageSize - 4;
                        break;
                    }
                    else
                    {
                        // Fill remaining data from the receive buffer
                        if (offset2 < size2)
                        {
                            // Copy message size into the storage buffer
                            if (!messageSizeCopied)
                            {
                                // Allocate and refresh the storage buffer
                                _buffer.allocate(4);
                                size1 += 4;

                                _buffer.write(_buffer.getData(), offset0, (int)messageSize);
                                offset0 += 4;
                                offset1 += 4;

                                messageSizeCopied = true;
                            }

                            count = Math.min(size2 - offset2, messageSize - 4 - count);

                            // Allocate and refresh the storage buffer
                            _buffer.allocate(count);
                            size1 += count;

                            System.arraycopy(buffer, (int)(offset + offset2), _buffer.getData(), (int)offset1, (int)count);
                            offset1 += count;
                            offset2 += count;
                            continue;
                        }
                        else
                            break;
                    }
                }

                // Look into the receive buffer
                if (offset2 < size2)
                {
                    long count = Math.min(size2 - offset2, messageSize - 4);
                    if (!messageSizeCopied && (count == (messageSize - 4)))
                    {
                        messageFound = true;
                        messageBuffer = buffer;
                        messageOffset = offset + offset2 - 4;
                        offset2 += messageSize - 4;
                        break;
                    }
                    else
                    {
                        // Copy message size into the storage buffer
                        if (!messageSizeCopied)
                        {
                            // Allocate and refresh the storage buffer
                            _buffer.allocate(4);
                            size1 += 4;

                            _buffer.write(_buffer.getData(), offset0, (int)messageSize);
                            offset0 += 4;
                            offset1 += 4;

                            messageSizeCopied = true;
                        }

                        // Allocate and refresh the storage buffer
                        _buffer.allocate(count);
                        size1 += count;

                        System.arraycopy(buffer, (int)(offset + offset2), _buffer.getData(), (int)offset1, (int)count);
                        offset1 += count;
                        offset2 += count;
                        continue;
                    }
                }
                else
                    break;
            }

            if (!messageFound)
            {
                // Copy message size into the storage buffer
                if (!messageSizeCopied)
                {
                    // Allocate and refresh the storage buffer
                    _buffer.allocate(4);
                    size1 += 4;

                    _buffer.write(_buffer.getData(), offset0, (int)messageSize);
                    offset0 += 4;
                    offset1 += 4;

                    messageSizeCopied = true;
                }
                return;
            }

            int fbeStructSize;
            int fbeStructType;

            // Read the message parameters
            if (_final)
            {
                fbeStructSize = Buffer.readInt32(messageBuffer, messageOffset);
                fbeStructType = Buffer.readInt32(messageBuffer, messageOffset + 4);
            }
            else
            {
                int fbeStructOffset = Buffer.readInt32(messageBuffer, messageOffset + 4);
                fbeStructSize = Buffer.readInt32(messageBuffer, messageOffset + fbeStructOffset);
                fbeStructType = Buffer.readInt32(messageBuffer, messageOffset + fbeStructOffset + 4);
            }

            // Handle the message
            onReceive(fbeStructType, messageBuffer, messageOffset, messageSize);

            // Reset the storage buffer
            _buffer.reset();

            // Refresh the storage buffer
            offset0 = _buffer.getOffset();
            offset1 = _buffer.getSize();
            size1 = _buffer.getSize();
        }
    }

    // Receive message handler
    public abstract boolean onReceive(long type, byte[] buffer, long offset, long size);
    // Receive log message handler
    protected void onReceiveLog(String message) {}
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateFBEJson(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "Json.java";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
final class ByteBufferJson implements com.google.gson.JsonSerializer<java.nio.ByteBuffer>, com.google.gson.JsonDeserializer<java.nio.ByteBuffer>
{
    @Override
    public com.google.gson.JsonElement serialize(java.nio.ByteBuffer src, java.lang.reflect.Type typeOfSrc, com.google.gson.JsonSerializationContext context)
    {
        return new com.google.gson.JsonPrimitive(java.util.Base64.getEncoder().encodeToString(src.array()));
    }

    @Override
    public java.nio.ByteBuffer deserialize(com.google.gson.JsonElement json, java.lang.reflect.Type type, com.google.gson.JsonDeserializationContext context) throws com.google.gson.JsonParseException
    {
        return java.nio.ByteBuffer.wrap(java.util.Base64.getDecoder().decode(json.getAsString()));
    }
}

final class CharacterJson implements com.google.gson.JsonSerializer<Character>, com.google.gson.JsonDeserializer<Character>
{
    @Override
    public com.google.gson.JsonElement serialize(Character src, java.lang.reflect.Type typeOfSrc, com.google.gson.JsonSerializationContext context)
    {
        return new com.google.gson.JsonPrimitive((long)src);
    }

    @Override
    public Character deserialize(com.google.gson.JsonElement json, java.lang.reflect.Type type, com.google.gson.JsonDeserializationContext context) throws com.google.gson.JsonParseException
    {
        return (char)json.getAsLong();
    }
}

final class DateJson implements com.google.gson.JsonSerializer<java.util.Date>, com.google.gson.JsonDeserializer<java.util.Date>
{
    @Override
    public com.google.gson.JsonElement serialize(java.util.Date src, java.lang.reflect.Type typeOfSrc, com.google.gson.JsonSerializationContext context)
    {
        long nanoseconds = src.getTime() * 1000;
        return new com.google.gson.JsonPrimitive(nanoseconds);
    }

    @Override
    public java.util.Date deserialize(com.google.gson.JsonElement json, java.lang.reflect.Type type, com.google.gson.JsonDeserializationContext context) throws com.google.gson.JsonParseException
    {
        long nanoseconds = json.getAsJsonPrimitive().getAsLong();
        return new java.util.Date(nanoseconds / 1000000);
    }
}

final class InstantJson implements com.google.gson.JsonSerializer<java.time.Instant>, com.google.gson.JsonDeserializer<java.time.Instant>
{
    @Override
    public com.google.gson.JsonElement serialize(java.time.Instant src, java.lang.reflect.Type typeOfSrc, com.google.gson.JsonSerializationContext context)
    {
        long nanoseconds = src.getEpochSecond() * 1000000000 + src.getNano();
        return new com.google.gson.JsonPrimitive(nanoseconds);
    }

    @Override
    public java.time.Instant deserialize(com.google.gson.JsonElement json, java.lang.reflect.Type type, com.google.gson.JsonDeserializationContext context) throws com.google.gson.JsonParseException
    {
        long nanoseconds = json.getAsJsonPrimitive().getAsLong();
        return java.time.Instant.ofEpochSecond(nanoseconds / 1000000000, nanoseconds % 1000000000);
    }
}

final class BigDecimalJson implements com.google.gson.JsonSerializer<java.math.BigDecimal>, com.google.gson.JsonDeserializer<java.math.BigDecimal>
{
    @Override
    public com.google.gson.JsonElement serialize(java.math.BigDecimal src, java.lang.reflect.Type typeOfSrc, com.google.gson.JsonSerializationContext context)
    {
        return new com.google.gson.JsonPrimitive(src.toPlainString());
    }

    @Override
    public java.math.BigDecimal deserialize(com.google.gson.JsonElement json, java.lang.reflect.Type type, com.google.gson.JsonDeserializationContext context) throws com.google.gson.JsonParseException
    {
        return new java.math.BigDecimal(json.getAsJsonPrimitive().getAsString());
    }
}

final class UUIDJson implements com.google.gson.JsonSerializer<java.util.UUID>, com.google.gson.JsonDeserializer<java.util.UUID>
{
    @Override
    public com.google.gson.JsonElement serialize(java.util.UUID src, java.lang.reflect.Type typeOfSrc, com.google.gson.JsonSerializationContext context)
    {
        return new com.google.gson.JsonPrimitive(src.toString());
    }

    @Override
    public java.util.UUID deserialize(com.google.gson.JsonElement json, java.lang.reflect.Type type, com.google.gson.JsonDeserializationContext context) throws com.google.gson.JsonParseException
    {
        return java.util.UUID.fromString(json.getAsJsonPrimitive().getAsString());
    }
}

// Fast Binary Encoding base JSON engine
public final class Json
{
    private static final com.google.gson.Gson _engine;

    // Get the JSON engine
    public static com.google.gson.Gson getEngine() { return _engine; }

    static
    {
        _engine = register(new com.google.gson.GsonBuilder()).create();
    }

    private Json() {}

    public static com.google.gson.GsonBuilder register(com.google.gson.GsonBuilder builder)
    {
        builder.serializeNulls();
        builder.registerTypeAdapter(java.nio.ByteBuffer.class, new ByteBufferJson());
        builder.registerTypeAdapter(char.class, new CharacterJson());
        builder.registerTypeAdapter(Character.class, new CharacterJson());
        builder.registerTypeAdapter(java.util.Date.class, new DateJson());
        builder.registerTypeAdapter(java.time.Instant.class, new InstantJson());
        builder.registerTypeAdapter(java.math.BigDecimal.class, new BigDecimalJson());
        builder.registerTypeAdapter(java.util.UUID.class, new UUIDJson());
        return builder;
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateContainers(const std::shared_ptr<Package>& p, bool final)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, *p->name);

    // Create package path
    CppCommon::Directory::CreateTree(path);

    if (p->body)
    {
        // Check all structs in the package
        for (const auto& s : p->body->structs)
        {
            if (s->body)
            {
                // Check all fields in the struct
                for (const auto& field : s->body->fields)
                {
                    if (field->array)
                    {
                        if (final)
                            GenerateFBEFinalModelArray(domain, *p->name, (field->optional ? "Optional" : "") + ConvertTypeFieldName(*field->type), ConvertTypeFieldType(domain, *field->type, field->optional), ConvertTypeFieldDeclaration(domain, *field->type, field->optional, final), ((*field->type == "byte") && !field->optional));
                        else
                            GenerateFBEFieldModelArray(domain, *p->name, (field->optional ? "Optional" : "") + ConvertTypeFieldName(*field->type), ConvertTypeFieldType(domain, *field->type, field->optional), ConvertTypeFieldDeclaration(domain, *field->type, field->optional, final), ((*field->type == "byte") && !field->optional));
                    }
                    if (field->vector || field->list || field->set)
                    {
                        if (final)
                            GenerateFBEFinalModelVector(domain, *p->name, (field->optional ? "Optional" : "") + ConvertTypeFieldName(*field->type), ConvertTypeFieldType(domain, *field->type, field->optional), ConvertTypeFieldDeclaration(domain, *field->type, field->optional, final));
                        else
                            GenerateFBEFieldModelVector(domain, *p->name, (field->optional ? "Optional" : "") + ConvertTypeFieldName(*field->type), ConvertTypeFieldType(domain, *field->type, field->optional), ConvertTypeFieldDeclaration(domain, *field->type, field->optional, final));
                    }
                    if (field->map || field->hash)
                    {
                        if (final)
                            GenerateFBEFinalModelMap(domain, *p->name, ConvertTypeFieldName(*field->key), ConvertTypeFieldType(domain, *field->key, false), ConvertTypeFieldDeclaration(domain, *field->key, false, final), (field->optional ? "Optional" : "") + ConvertTypeFieldName(*field->type), ConvertTypeFieldType(domain, *field->type, field->optional), ConvertTypeFieldDeclaration(domain, *field->type, field->optional, final));
                        else
                            GenerateFBEFieldModelMap(domain, *p->name, ConvertTypeFieldName(*field->key), ConvertTypeFieldType(domain, *field->key, false), ConvertTypeFieldDeclaration(domain, *field->key, false, final), (field->optional ? "Optional" : "") + ConvertTypeFieldName(*field->type), ConvertTypeFieldType(domain, *field->type, field->optional), ConvertTypeFieldDeclaration(domain, *field->type, field->optional, final));
                    }
                    if (field->optional)
                    {
                        if (final)
                            GenerateFBEFinalModelOptional(domain, *p->name, ConvertTypeFieldName(*field->type), ConvertTypeFieldType(domain, *field->type, field->optional), ConvertTypeFieldDeclaration(domain, *field->type, false, final));
                        else
                            GenerateFBEFieldModelOptional(domain, *p->name, ConvertTypeFieldName(*field->type), ConvertTypeFieldType(domain, *field->type, field->optional), ConvertTypeFieldDeclaration(domain, *field->type, false, final));
                    }
                }
            }
        }
    }
}

void GeneratorJava::GeneratePackage(const std::shared_ptr<Package>& p)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, *p->name);

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate namespace
    if (p->body)
    {
        // Generate child enums
        for (const auto& child_e : p->body->enums)
            GenerateEnum(p, child_e, path);

        // Generate child flags
        for (const auto& child_f : p->body->flags)
            GenerateFlags(p, child_f, path);

        // Generate child structs
        for (const auto& child_s : p->body->structs)
            GenerateStruct(p, child_s, path);
    }

    // Generate containers
    GenerateContainers(p, false);
    if (Final())
        GenerateContainers(p, true);

    // Generate protocol
    if (Proto())
    {
        // Generate protocol version
        GenerateProtocolVersion(p);

        // Generate sender & receiver
        GenerateSender(p, false);
        GenerateReceiver(p, false);
        GenerateProxy(p, false);
        if (Final())
        {
            GenerateSender(p, true);
            GenerateReceiver(p, true);
        }
    }

    // Generate JSON engine
    if (JSON())
        GenerateJson(p);
}

void GeneratorJava::GenerateEnum(const std::shared_ptr<Package>& p, const std::shared_ptr<EnumType>& e, const CppCommon::Path& path)
{
    std::string enum_name = *e->name + "Enum";

    // Generate the output file
    CppCommon::Path output = path / (enum_name + ".java");
    WriteBegin();

    // Generate enum header
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(p);

    std::string enum_type = (e->base && !e->base->empty()) ? *e->base : "int32";
    std::string enum_base_type = ConvertEnumType(enum_type);
    std::string enum_mapping_type = ConvertEnumBase(enum_type);

    // Generate enum body
    WriteLine();
    WriteLineIndent("public enum " + enum_name);
    WriteLineIndent("{");
    Indent(1);
    if (e->body)
    {
        int index = 0;
        bool first = true;
        std::string last = ConvertEnumConstant(enum_type, "0");
        for (const auto& value : e->body->values)
        {
            WriteIndent(std::string(first ? "" : ", ") + *value->name + "(");
            if (value->value)
            {
                if (value->value->constant && !value->value->constant->empty())
                {
                    index = 0;
                    last = ConvertEnumConstant(enum_type, *value->value->constant);
                    Write(last + " + " + std::to_string(index++));
                }
                else if (value->value->reference && !value->value->reference->empty())
                {
                    index = 0;
                    last = ConvertEnumConstant("", *value->value->reference);
                    Write(last);
                }
            }
            else
                Write(last + " + " + std::to_string(index++));
            WriteLine(")");
            first = false;
        }
        WriteLineIndent(";");
        WriteLine();
    }

    // Generate enum value
    WriteLineIndent("private " + enum_base_type + " value;");

    // Generate enum constructors
    WriteLine();
    if (enum_base_type != "int")
        WriteLineIndent(enum_name + "(" + enum_base_type + " value) { this.value = value; }");
    WriteLineIndent(enum_name + "(int value) { this.value = (" + enum_base_type + ")value; }");
    WriteLineIndent(enum_name + "(" + enum_name + " value) { this.value = value.value; }");

    // Generate enum getRaw() method
    WriteLine();
    WriteLineIndent("public " + enum_base_type + " getRaw() { return value; }");

    // Generate enum mapValue() method
    WriteLine();
    WriteLineIndent("public static " + enum_name + " mapValue(" + enum_base_type + " value) { return mapping.get(value); }");

    // Generate enum toString() method
    WriteLine();
    WriteLineIndent("@Override");
    WriteLineIndent("public String toString()");
    WriteLineIndent("{");
    Indent(1);
    if (e->body)
    {
        for (const auto& value : e->body->values)
            WriteLineIndent("if (this == " + *value->name + ")" + " return \"" + *value->name + "\";");

    }
    WriteLineIndent("return \"<unknown>\";");
    Indent(-1);
    WriteLineIndent("}");

    // Generate enum mapping
    WriteLine();
    WriteLineIndent("private static final java.util.Map<" + enum_mapping_type + ", " + enum_name + "> mapping = new java.util.HashMap<>();");
    WriteLineIndent("static");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("for (var value : " + enum_name + ".values())");
    Indent(1);
    WriteLineIndent("mapping.put(value.value, value);");
    Indent(-1);
    Indent(-1);
    WriteLineIndent("}");

    Indent(-1);
    WriteLineIndent("}");

    // Generate enum footer
    GenerateFooter();

    // Store the output file
    WriteEnd();
    Store(output);

    // Generate enum wrapper class
    GenerateEnumClass(p, e, path);

    // Generate enum JSON adapter
    if (JSON())
        GenerateEnumJson(p, e);

    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";

    // Generate enum field model
    GenerateFBEFieldModelEnumFlags(domain, *p->name, *e->name, enum_type);

    // Generate enum final model
    if (Final())
        GenerateFBEFinalModelEnumFlags(domain, *p->name, *e->name, enum_type);
}

void GeneratorJava::GenerateEnumClass(const std::shared_ptr<Package>& p, const std::shared_ptr<EnumType>& e, const CppCommon::Path& path)
{
    std::string enum_name = *e->name;
    std::string enum_type_name = *e->name + "Enum";

    // Generate the output file
    CppCommon::Path output = path / (enum_name + ".java");
    WriteBegin();

    // Generate enum class header
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(p);

    std::string enum_type = (e->base && !e->base->empty()) ? *e->base : "int32";
    std::string enum_base_type = ConvertEnumType(enum_type);

    // Generate enum class body
    WriteLine();
    WriteLineIndent("public final class " + enum_name + " implements Comparable<" + enum_name + ">");
    WriteLineIndent("{");
    Indent(1);
    if (e->body)
    {
        for (const auto& value : e->body->values)
            WriteLineIndent("public static final " + enum_name + " " + *value->name + " = new " + enum_name + "(" + enum_type_name + "." + *value->name + ");");
        WriteLine();
    }

    // Generate enum class value
    WriteLineIndent("private " + enum_type_name + " value = " + enum_type_name + ".values()[0];");

    // Generate enum class constructors
    WriteLine();
    WriteLineIndent("public " + enum_name + "() {}");
    WriteLineIndent("public " + enum_name + "(" + enum_base_type + " value) { setEnum(value); }");
    WriteLineIndent("public " + enum_name + "(" + enum_type_name + " value) { setEnum(value); }");
    WriteLineIndent("public " + enum_name + "(" + enum_name + " value) { setEnum(value); }");

    // Generate enum class getEnum() and getRaw() methods
    WriteLine();
    WriteLineIndent("public " + enum_type_name + " getEnum() { return value; }");
    WriteLineIndent("public " + enum_base_type + " getRaw() { return value.getRaw(); }");

    // Generate enum class setDefault() method
    WriteLine();
    WriteLineIndent("public void setDefault() { setEnum((" + enum_base_type + ")0); }");

    // Generate enum class setEnum() methods
    WriteLine();
    WriteLineIndent("public void setEnum(" + enum_base_type + " value) { this.value = " + enum_type_name + ".mapValue(value); }");
    WriteLineIndent("public void setEnum(" + enum_type_name + " value) { this.value = value; }");
    WriteLineIndent("public void setEnum(" + enum_name + " value) { this.value = value.value; }");

    // Generate enum class compareTo() method
    WriteLine();
    WriteLineIndent("@Override");
    WriteLineIndent("public int compareTo(" + enum_name + " other)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if (value == null)");
    Indent(1);
    WriteLineIndent("return -1;");
    Indent(-1);
    WriteLineIndent("if (other.value == null)");
    Indent(1);
    WriteLineIndent("return 1;");
    Indent(-1);
    WriteLineIndent("return (int)(value.getRaw() - other.value.getRaw());");
    Indent(-1);
    WriteLineIndent("}");

    // Generate enum class equals() method
    WriteLine();
    WriteLineIndent("@Override");
    WriteLineIndent("public boolean equals(Object other)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if (other == null)");
    Indent(1);
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("if (!" + enum_name + ".class.isAssignableFrom(other.getClass()))");
    Indent(1);
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("final " + enum_name + " enm = (" + enum_name + ")other;");
    WriteLine();
    WriteLineIndent("if ((value == null) || (enm.value == null))");
    Indent(1);
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLineIndent("if (value.getRaw() != enm.value.getRaw())");
    Indent(1);
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLineIndent("return true;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate enum class hashCode() method
    WriteLine();
    WriteLineIndent("@Override");
    WriteLineIndent("public int hashCode()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("int hash = 17;");
    WriteLineIndent("hash = hash * 31 + ((value != null) ? value.hashCode() : 0);");
    WriteLineIndent("return hash;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate enum class toString() method
    WriteLine();
    WriteLineIndent("@Override");
    WriteLineIndent("public String toString() { return (value != null) ? value.toString() : \"<unknown>\"; }");

    Indent(-1);
    WriteLineIndent("}");

    // Generate enum class footer
    GenerateFooter();

    // Store the output file
    WriteEnd();
    Store(output);
}

void GeneratorJava::GenerateEnumJson(const std::shared_ptr<Package>& p, const std::shared_ptr<EnumType>& e)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";
    std::string package = *p->name;
    std::string enum_name = domain + package + "." + *e->name;
    std::string adapter_name = *e->name + "Json";

    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the output file
    CppCommon::Path output = path / (adapter_name + ".java");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe;");

    std::string enum_type = (e->base && !e->base->empty()) ? *e->base : "int32";

    // Generate JSON adapter body
    WriteLine();
    WriteLineIndent("public final class " + adapter_name + " implements com.google.gson.JsonSerializer<" + enum_name + ">, com.google.gson.JsonDeserializer<" + enum_name + ">");
    WriteLineIndent("{");
    Indent(1);

    // Generate JSON adapter serialize() method
    WriteLineIndent("@Override");
    WriteLineIndent("public com.google.gson.JsonElement serialize(" + enum_name + " src, java.lang.reflect.Type typeOfSrc, com.google.gson.JsonSerializationContext context)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("return new com.google.gson.JsonPrimitive(src.getRaw());");
    Indent(-1);
    WriteLineIndent("}");

    // Generate JSON adapter deserialize() method
    WriteLine();
    WriteLineIndent("@Override");
    WriteLineIndent("public " + enum_name + " deserialize(com.google.gson.JsonElement json, java.lang.reflect.Type type, com.google.gson.JsonDeserializationContext context) throws com.google.gson.JsonParseException");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("return new " + enum_name + "(json.getAsJsonPrimitive()." + ConvertEnumGet(enum_type) + "());");
    Indent(-1);
    WriteLineIndent("}");

    Indent(-1);
    WriteLineIndent("}");

    // Generate JSON adapter footer
    GenerateFooter();

    // Store the output file
    WriteEnd();
    Store(output);
}

void GeneratorJava::GenerateFlags(const std::shared_ptr<Package>& p, const std::shared_ptr<FlagsType>& f, const CppCommon::Path& path)
{
    std::string flags_name = *f->name + "Enum";

    // Generate the output file
    CppCommon::Path output = path / (flags_name + ".java");
    WriteBegin();

    // Generate flags header
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(p);

    std::string flags_type = (f->base && !f->base->empty()) ? *f->base : "int32";
    std::string flags_base_type = ConvertEnumType(flags_type);
    std::string flags_mapping_type = ConvertEnumBase(flags_type);

    // Generate flags body
    WriteLine();
    WriteLineIndent("public enum " + flags_name);
    WriteLineIndent("{");
    Indent(1);
    if (f->body)
    {
        bool first = true;
        for (const auto& value : f->body->values)
        {
            WriteIndent(std::string(first ? "" : ", ") + *value->name + "(");
            if (value->value)
            {
                if (value->value->constant && !value->value->constant->empty())
                    Write(ConvertEnumConstant(flags_type, *value->value->constant));
                else if (value->value->reference && !value->value->reference->empty())
                    Write(ConvertEnumConstant("", *value->value->reference));
            }
            WriteLine(")");
            first = false;
        }
        WriteLineIndent(";");
        WriteLine();
    }
    else
        WriteIndent("unknown(0);");

    // Generate flags class value
    WriteLineIndent("private " + flags_base_type + " value;");

    // Generate flags class constructors
    WriteLine();
    if (flags_base_type != "int")
        WriteLineIndent(flags_name + "(" + flags_base_type + " value) { this.value = value; }");
    WriteLineIndent(flags_name + "(int value) { this.value = (" + flags_base_type + ")value; }");
    WriteLineIndent(flags_name + "(" + flags_name + " value) { this.value = value.value; }");

    // Generate flags getRaw() method
    WriteLine();
    WriteLineIndent("public " + flags_base_type + " getRaw() { return value; }");

    // Generate flags mapValue() method
    WriteLine();
    WriteLineIndent("public static " + flags_name + " mapValue(" + flags_base_type + " value) { return mapping.get(value); }");

    // Generate flags hasFlags() methods
    WriteLine();
    WriteLineIndent("public boolean hasFlags(" + flags_base_type + " flags) { return (((value & flags) != 0) && ((value & flags) == flags)); }");
    WriteLineIndent("public boolean hasFlags(" + flags_name + " flags) { return hasFlags(flags.value); }");

    // Generate flags getAllSet(), getNoneSet(), getCurrentSet() methods
    WriteLine();
    WriteLineIndent("public java.util.EnumSet<" + flags_name + "> getAllSet() { return java.util.EnumSet.allOf(" + flags_name + ".class); }");
    WriteLineIndent("public java.util.EnumSet<" + flags_name + "> getNoneSet() { return java.util.EnumSet.noneOf(" + flags_name + ".class); }");
    WriteLineIndent("public java.util.EnumSet<" + flags_name + "> getCurrentSet()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("java.util.EnumSet<" + flags_name + "> result = java.util.EnumSet.noneOf(" + flags_name + ".class);");
    if (f->body)
    {
        for (const auto& value : f->body->values)
        {
            WriteLineIndent("if ((value & " + *value->name + ".getRaw()) != 0)");
            WriteLineIndent("{");
            Indent(1);
            WriteLineIndent("result.add(" + *value->name + ");");
            Indent(-1);
            WriteLineIndent("}");
        }
    }
    WriteLineIndent("return result;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate flags toString() method
    WriteLine();
    WriteLineIndent("@Override");
    WriteLineIndent("public String toString()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("var sb = new StringBuilder();");
    WriteLineIndent("boolean first = true;");
    if (f->body)
    {
        for (const auto& value : f->body->values)
        {
            WriteLineIndent("if (hasFlags(" + *value->name + "))");
            WriteLineIndent("{");
            Indent(1);
            WriteLineIndent("sb.append(first ? \"\" : \"|\").append(\"" + *value->name + "\");");
            WriteLineIndent("first = false;");
            Indent(-1);
            WriteLineIndent("}");
        }
    }
    WriteLineIndent("return sb.toString();");
    Indent(-1);
    WriteLineIndent("}");

    // Generate flags mapping
    WriteLine();
    WriteLineIndent("private static final java.util.Map<" + flags_mapping_type + ", " + flags_name + "> mapping = new java.util.HashMap<>();");
    WriteLineIndent("static");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("for (var value : " + flags_name + ".values())");
    Indent(1);
    WriteLineIndent("mapping.put(value.value, value);");
    Indent(-1);
    Indent(-1);
    WriteLineIndent("}");

    Indent(-1);
    WriteLineIndent("}");

    // Generate flags footer
    GenerateFooter();

    // Store the output file
    WriteEnd();
    Store(output);

    // Generate flags wrapper class
    GenerateFlagsClass(p, f, path);

    // Generate flags JSON adapter
    if (JSON())
        GenerateFlagsJson(p, f);

    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";

    // Generate flags field model
    GenerateFBEFieldModelEnumFlags(domain, *p->name, *f->name, flags_type);

    // Generate flags final model
    if (Final())
        GenerateFBEFinalModelEnumFlags(domain, *p->name, *f->name, flags_type);
}

void GeneratorJava::GenerateFlagsClass(const std::shared_ptr<Package>& p, const std::shared_ptr<FlagsType>& f, const CppCommon::Path& path)
{
    std::string flags_name = *f->name;
    std::string flags_type_name = *f->name + "Enum";

    // Generate the output file
    CppCommon::Path output = path / (flags_name + ".java");
    WriteBegin();

    // Generate flags class header
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(p);

    std::string flags_type = (f->base && !f->base->empty()) ? *f->base : "int32";
    std::string flags_base_type = ConvertEnumType(flags_type);

    // Generate flags class body
    WriteLine();
    WriteLineIndent("public final class " + flags_name + " implements Comparable<" + flags_name + ">");
    WriteLineIndent("{");
    Indent(1);
    if (f->body)
    {
        for (const auto& value : f->body->values)
            WriteLineIndent("public static final " + flags_name + " " + *value->name + " = new " + flags_name + "(" + flags_type_name + "." + *value->name + ");");
        WriteLine();
    }

    // Generate flags class value and flags
    WriteLineIndent("private " + flags_type_name + " value = " + flags_type_name + ".values()[0];");
    WriteLineIndent("private " + flags_base_type + " flags = value.getRaw();");

    // Generate flags class constructors
    WriteLine();
    WriteLineIndent("public " + flags_name + "() {}");
    WriteLineIndent("public " + flags_name + "(" + flags_base_type + " value) { setEnum(value); }");
    WriteLineIndent("public " + flags_name + "(" + flags_type_name + " value) { setEnum(value); }");
    WriteLineIndent("public " + flags_name + "(java.util.EnumSet<" + flags_type_name + "> value) { setEnum(value); }");
    WriteLineIndent("public " + flags_name + "(" + flags_name + " value) { setEnum(value); }");

    // Generate flags class getEnum() and getRaw() methods
    WriteLine();
    WriteLineIndent("public " + flags_type_name + " getEnum() { return value; }");
    WriteLineIndent("public " + flags_base_type + " getRaw() { return flags; }");

    // Generate flags class setDefault() method
    WriteLine();
    WriteLineIndent("public void setDefault() { setEnum((" + flags_base_type + ")0); }");

    // Generate flags class setEnum() methods
    WriteLine();
    WriteLineIndent("public void setEnum(" + flags_base_type + " value) { this.flags = value; this.value = " + flags_type_name + ".mapValue(value); }");
    WriteLineIndent("public void setEnum(" + flags_type_name + " value) { this.value = value; this.flags = value.getRaw(); }");
    WriteLineIndent("public void setEnum(java.util.EnumSet<" + flags_type_name + "> value) { setEnum(" + flags_name + ".fromSet(value)); }");
    WriteLineIndent("public void setEnum(" + flags_name + " value) { this.value = value.value; this.flags = value.flags; }");

    // Generate flags class hasFlags() methods
    WriteLine();
    WriteLineIndent("public boolean hasFlags(" + flags_base_type + " flags) { return (((this.flags & flags) != 0) && ((this.flags & flags) == flags)); }");
    WriteLineIndent("public boolean hasFlags(" + flags_type_name + " flags) { return hasFlags(flags.getRaw()); }");
    WriteLineIndent("public boolean hasFlags(" + flags_name + " flags) { return hasFlags(flags.flags); }");

    // Generate flags class setFlags() methods
    WriteLine();
    WriteLineIndent("public " + flags_name + " setFlags(" + flags_base_type + " flags) { setEnum((" + flags_base_type + ")(this.flags | flags)); return this; }");
    WriteLineIndent("public " + flags_name + " setFlags(" + flags_type_name + " flags) { setFlags(flags.getRaw()); return this; }");
    WriteLineIndent("public " + flags_name + " setFlags(" + flags_name + " flags) { setFlags(flags.flags); return this; }");

    // Generate flags class removeFlags() methods
    WriteLine();
    WriteLineIndent("public " + flags_name + " removeFlags(" + flags_base_type + " flags) { setEnum((" + flags_base_type + ")(this.flags & ~flags)); return this; }");
    WriteLineIndent("public " + flags_name + " removeFlags(" + flags_type_name + " flags) { removeFlags(flags.getRaw()); return this; }");
    WriteLineIndent("public " + flags_name + " removeFlags(" + flags_name + " flags) { removeFlags(flags.flags); return this; }");

    // Generate flags class getAllSet(), getNoneSet() and getCurrentSet() methods
    WriteLine();
    WriteLineIndent("public java.util.EnumSet<" + flags_type_name + "> getAllSet() { return value.getAllSet(); }");
    WriteLineIndent("public java.util.EnumSet<" + flags_type_name + "> getNoneSet() { return value.getNoneSet(); }");
    WriteLineIndent("public java.util.EnumSet<" + flags_type_name + "> getCurrentSet() { return value.getCurrentSet(); }");

    // Generate flags class fromSet() method
    WriteLine();
    WriteLineIndent("public static " + flags_name + " fromSet(java.util.EnumSet<" + flags_type_name + "> set)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent(flags_base_type + " result = 0;");
    if (f->body)
    {
        for (const auto& value : f->body->values)
        {
            WriteLineIndent("if (set.contains(" + *value->name + ".getEnum()))");
            WriteLineIndent("{");
            Indent(1);
            WriteLineIndent("result |= " + *value->name + ".flags;");
            Indent(-1);
            WriteLineIndent("}");
        }
    }
    WriteLineIndent("return new " + flags_name + "(result);");
    Indent(-1);
    WriteLineIndent("}");

    // Generate flags class compareTo() method
    WriteLine();
    WriteLineIndent("@Override");
    WriteLineIndent("public int compareTo(" + flags_name + " other)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("return (int)(flags - other.flags);");
    Indent(-1);
    WriteLineIndent("}");

    // Generate flags class equals() method
    WriteLine();
    WriteLineIndent("@Override");
    WriteLineIndent("public boolean equals(Object other)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if (other == null)");
    Indent(1);
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("if (!" + flags_name + ".class.isAssignableFrom(other.getClass()))");
    Indent(1);
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("final " + flags_name + " flg = (" + flags_name + ")other;");
    WriteLine();
    WriteLineIndent("if (flags != flg.flags)");
    Indent(1);
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLineIndent("return true;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate flags class hashCode() method
    WriteLine();
    WriteLineIndent("@Override");
    WriteLineIndent("public int hashCode()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("int hash = 17;");
    WriteLineIndent("hash = hash * 31 + (int)flags;");
    WriteLineIndent("return hash;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate flags class toString() method
    WriteLine();
    WriteLineIndent("@Override");
    WriteLineIndent("public String toString()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("var sb = new StringBuilder();");
    WriteLineIndent("boolean first = true;");
    if (f->body)
    {
        for (const auto& value : f->body->values)
        {
            WriteLineIndent("if (hasFlags(" + *value->name + ".flags))");
            WriteLineIndent("{");
            Indent(1);
            WriteLineIndent("sb.append(first ? \"\" : \"|\").append(\"" + *value->name + "\");");
            WriteLineIndent("first = false;");
            Indent(-1);
            WriteLineIndent("}");
        }
    }
    WriteLineIndent("return sb.toString();");
    Indent(-1);
    WriteLineIndent("}");

    Indent(-1);
    WriteLineIndent("}");

    // Generate flags class footer
    GenerateFooter();

    // Store the output file
    WriteEnd();
    Store(output);
}

void GeneratorJava::GenerateFlagsJson(const std::shared_ptr<Package>& p, const std::shared_ptr<FlagsType>& f)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";
    std::string package = *p->name;
    std::string flags_name = domain + package + "." + *f->name;
    std::string adapter_name = *f->name + "Json";

    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the output file
    CppCommon::Path output = path / (adapter_name + ".java");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe;");

    std::string flags_type = (f->base && !f->base->empty()) ? *f->base : "int32";

    // Generate JSON adapter body
    WriteLine();
    WriteLineIndent("public final class " + adapter_name + " implements com.google.gson.JsonSerializer<" + flags_name + ">, com.google.gson.JsonDeserializer<" + flags_name + ">");
    WriteLineIndent("{");
    Indent(1);

    // Generate JSON adapter serialize() method
    WriteLine();
    WriteLineIndent("@Override");
    WriteLineIndent("public com.google.gson.JsonElement serialize(" + flags_name + " src, java.lang.reflect.Type typeOfSrc, com.google.gson.JsonSerializationContext context)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("return new com.google.gson.JsonPrimitive(src.getRaw());");
    Indent(-1);
    WriteLineIndent("}");

    // Generate JSON adapter deserialize() method
    WriteLine();
    WriteLineIndent("@Override");
    WriteLineIndent("public " + flags_name + " deserialize(com.google.gson.JsonElement json, java.lang.reflect.Type type, com.google.gson.JsonDeserializationContext context) throws com.google.gson.JsonParseException");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("return new " + flags_name + "(json.getAsJsonPrimitive()." + ConvertEnumGet(flags_type) + "());");
    Indent(-1);
    WriteLineIndent("}");

    Indent(-1);
    WriteLineIndent("}");

    // Generate JSON adapter footer
    GenerateFooter();

    // Store the output file
    WriteEnd();
    Store(output);
}

void GeneratorJava::GenerateStruct(const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s, const CppCommon::Path& path)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";
    bool first;

    // Generate the output file
    CppCommon::Path output = path / (*s->name + ".java");
    WriteBegin();

    // Generate struct header
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(p);

    // Generate struct begin
    WriteLine();
    WriteIndent("public class " + *s->name);
    if (s->base && !s->base->empty())
        Write(" extends " + ConvertTypeName(domain, "", *s->base, false));
    else
        Write(" implements Comparable<Object>");
    WriteLine();
    WriteLineIndent("{");
    Indent(1);

    // Generate struct body
    if (s->body && !s->body->fields.empty())
    {
        for (const auto& field : s->body->fields)
            WriteLineIndent("public " + ConvertTypeName(domain, "", *field) + " " + *field->name + " = " + ConvertDefault(domain, "", *field) + ";");
        WriteLine();
    }

    // Generate struct FBE type property
    if (s->base && !s->base->empty() && (s->type == 0))
        WriteLineIndent("public static final long fbeTypeConst = " + ConvertTypeName(domain, "", *s->base, false) + ".fbeTypeConst;");
    else
        WriteLineIndent("public static final long fbeTypeConst = " + std::to_string(s->type) + ";");
    WriteLineIndent("public long fbeType() { return fbeTypeConst; }");

    // Generate struct default constructor
    WriteLine();
    WriteLineIndent("public " + *s->name + "() {}");

    // Generate struct initialization constructor
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        first = true;
        WriteLine();
        WriteIndent("public " + *s->name + "(");
        if (s->base && !s->base->empty())
        {
            Write(ConvertTypeName(domain, "", *s->base, false) + " parent");
            first = false;
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                Write(std::string(first ? "" : ", ") + ConvertTypeName(domain, "", *field) + " " + *field->name);
                first = false;
            }
        }
        WriteLine(")");
        WriteLineIndent("{");
        Indent(1);
        if (s->base && !s->base->empty())
            WriteLineIndent("super(parent);");
        if (s->body)
            for (const auto& field : s->body->fields)
                WriteLineIndent("this." + *field->name + " = " + *field->name + ";");
        Indent(-1);
        WriteLineIndent("}");
    }

    // Generate struct copy constructor
    WriteLine();
    WriteLineIndent("public " + *s->name + "(" + *s->name +" other)");
    WriteLineIndent("{");
    Indent(1);
    if (s->base && !s->base->empty())
        WriteLineIndent("super(other);");
    if (s->body)
        for (const auto& field : s->body->fields)
            WriteLineIndent("this." + *field->name + " = other." + *field->name + ";");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct clone() method
    WriteLine();
    WriteLineIndent("public " + *s->name + " clone()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("// Serialize the struct to the FBE stream");
    WriteLineIndent("var writer = new " + domain + *p->name + ".fbe." + *s->name + "Model();");
    WriteLineIndent("writer.serialize(this);");
    WriteLine();
    WriteLineIndent("// Deserialize the struct from the FBE stream");
    WriteLineIndent("var reader = new " + domain + *p->name + ".fbe." + *s->name + "Model();");
    WriteLineIndent("reader.attach(writer.getBuffer());");
    WriteLineIndent("return reader.deserialize();");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct compareTo() method
    WriteLine();
    WriteLineIndent("@Override");
    WriteLineIndent("public int compareTo(Object other)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if (other == null)");
    Indent(1);
    WriteLineIndent("return -1;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("if (!" + *s->name + ".class.isAssignableFrom(other.getClass()))");
    Indent(1);
    WriteLineIndent("return -1;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("final " + *s->name + " obj = (" + *s->name + ")other;");
    WriteLine();
    WriteLineIndent("int result = 0;");
    if (s->base && !s->base->empty())
    {
        WriteLineIndent("result = super.compareTo(obj);");
        WriteLineIndent("if (result != 0)");
        Indent(1);
        WriteLineIndent("return result;");
        Indent(-1);
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            if (field->keys)
            {
                if (IsPrimitiveType(*field->type, field->optional))
                    WriteLineIndent("result = " + ConvertPrimitiveTypeName(*field->type)  + ".compare(" + *field->name + ", obj." + *field->name + ");");
                else
                    WriteLineIndent("result = " + *field->name + ".compareTo(obj." + *field->name + ");");
                WriteLineIndent("if (result != 0)");
                Indent(1);
                WriteLineIndent("return result;");
                Indent(-1);
            }
        }
    }
    WriteLineIndent("return result;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct equals() method
    WriteLine();
    WriteLineIndent("@Override");
    WriteLineIndent("public boolean equals(Object other)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if (other == null)");
    Indent(1);
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("if (!" + *s->name + ".class.isAssignableFrom(other.getClass()))");
    Indent(1);
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("final " + *s->name + " obj = (" + *s->name + ")other;");
    WriteLine();
    if (s->base && !s->base->empty())
    {
        WriteLineIndent("if (!super.equals(obj))");
        Indent(1);
        WriteLineIndent("return false;");
        Indent(-1);
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            if (field->keys)
            {
                if (IsPrimitiveType(*field->type, field->optional))
                    WriteLineIndent("if (" + *field->name + " != obj." + *field->name + ")");
                else
                    WriteLineIndent("if (!" + *field->name + ".equals(obj." + *field->name + "))");
                Indent(1);
                WriteLineIndent("return false;");
                Indent(-1);
            }
        }
    }
    WriteLineIndent("return true;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct hashCode() method
    WriteLine();
    WriteLineIndent("@Override");
    WriteLineIndent("public int hashCode()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("int hash = 17;");
    if (s->base && !s->base->empty())
        WriteLineIndent("hash = hash * 31 + super.hashCode();");
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            if (field->keys)
            {
                if (IsPrimitiveType(*field->type, field->optional))
                    WriteLineIndent("hash = hash * 31 + " + ConvertPrimitiveTypeName(*field->type) + ".hashCode(" + *field->name + ");");
                else
                    WriteLineIndent("hash = hash * 31 + " + *field->name + ".hashCode();");
            }
        }
    }
    WriteLineIndent("return hash;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct toString() method
    WriteLine();
    WriteLineIndent("@Override");
    WriteLineIndent("public String toString()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("var sb = new StringBuilder();");
    WriteLineIndent("sb.append(\"" + *s->name + "(\");");
    first = true;
    if (s->base && !s->base->empty())
    {
        WriteLineIndent("sb.append(super.toString());");
        first = false;
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            if (field->attributes && field->attributes->hidden)
                WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=***\");");
            else if (field->array)
            {
                WriteLineIndent("if (" + *field->name + " != null)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("boolean first = true;");
                WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=[\").append(" + *field->name + ".length" + ").append(\"][\");");
                WriteLineIndent("for (var item : " + *field->name + ")");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent(ConvertOutputStreamValue(*field->type, "item", field->optional, true));
                WriteLineIndent("first = false;");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("sb.append(\"]\");");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("else");
                Indent(1);
                WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=[0][]\");");
                Indent(-1);
            }
            else if (field->vector)
            {
                WriteLineIndent("if (" + *field->name + " != null)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("boolean first = true;");
                WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=[\").append(" + *field->name + ".size()" + ").append(\"][\");");
                WriteLineIndent("for (var item : " + *field->name + ")");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent(ConvertOutputStreamValue(*field->type, "item", field->optional, true));
                WriteLineIndent("first = false;");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("sb.append(\"]\");");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("else");
                Indent(1);
                WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=[0][]\");");
                Indent(-1);
            }
            else if (field->list)
            {
                WriteLineIndent("if (" + *field->name + " != null)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("boolean first = true;");
                WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=[\").append(" + *field->name + ".size()" + ").append(\"]<\");");
                WriteLineIndent("for (var item : " + *field->name + ")");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent(ConvertOutputStreamValue(*field->type, "item", field->optional, true));
                WriteLineIndent("first = false;");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("sb.append(\">\");");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("else");
                Indent(1);
                WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=[0]<>\");");
                Indent(-1);
            }
            else if (field->set)
            {
                WriteLineIndent("if (" + *field->name + " != null)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("boolean first = true;");
                WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=[\").append(" + *field->name + ".size()" + ").append(\"]{\");");
                WriteLineIndent("for (var item : " + *field->name + ")");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent(ConvertOutputStreamValue(*field->type, "item", field->optional, true));
                WriteLineIndent("first = false;");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("sb.append(\"}\");");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("else");
                Indent(1);
                WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=[0]{}\");");
                Indent(-1);
            }
            else if (field->map)
            {
                WriteLineIndent("if (" + *field->name + " != null)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("boolean first = true;");
                WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=[\").append(" + *field->name + ".size()" + ").append(\"]<{\");");
                WriteLineIndent("for (var item : " + *field->name + ".entrySet())");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent(ConvertOutputStreamValue(*field->key, "item.getKey()", false, true));
                WriteLineIndent("sb.append(\"->\");");
                WriteLineIndent(ConvertOutputStreamValue(*field->type, "item.getValue()", field->optional, false));
                WriteLineIndent("first = false;");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("sb.append(\"}>\");");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("else");
                Indent(1);
                WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=[0]<{}>\");");
                Indent(-1);
            }
            else if (field->hash)
            {
                WriteLineIndent("if (" + *field->name + " != null)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("boolean first = true;");
                WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=[\").append(" + *field->name + ".size()" + ").append(\"][{\");");
                WriteLineIndent("for (var item : " + *field->name + ".entrySet())");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent(ConvertOutputStreamValue(*field->key, "item.getKey()", false, true));
                WriteLineIndent("sb.append(\"->\");");
                WriteLineIndent(ConvertOutputStreamValue(*field->type, "item.getValue()", field->optional, false));
                WriteLineIndent("first = false;");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("sb.append(\"}]\");");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("else");
                Indent(1);
                WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=[0][{}]\");");
                Indent(-1);
            }
            else
                WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=\"); " + ConvertOutputStreamValue(*field->type, *field->name, field->optional, false));
            first = false;
        }
    }
    WriteLineIndent("sb.append(\")\");");
    WriteLineIndent("return sb.toString();");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct JSON methods
    if (JSON())
    {
        WriteLine();
        WriteLineIndent("public String toJson() { return " + domain + *p->name + ".fbe.Json.getEngine().toJson(this); }");
        WriteLineIndent("public static " + *s->name + " fromJson(String json) { return " + domain + *p->name + ".fbe.Json.getEngine().fromJson(json, " + *s->name + ".class); }");
    }

    // Generate struct end
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct footer
    GenerateFooter();

    // Store the output file
    WriteEnd();
    Store(output);

    // Generate struct field models
    GenerateStructFieldModel(p, s);
    GenerateStructModel(p, s);

    // Generate struct final models
    if (Final())
    {
        GenerateStructFinalModel(p, s);
        GenerateStructModelFinal(p, s);
    }
}

void GeneratorJava::GenerateStructFieldModel(const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";
    std::string package = *p->name;
    std::string struct_name = domain + *p->name + "." + *s->name;

    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the file
    CppCommon::Path file = path / ("FieldModel" + *s->name + ".java");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    // Generate struct field model begin
    WriteLine();
    WriteLineIndent("// Fast Binary Encoding " + *s->name + " field model");
    WriteLineIndent("public final class FieldModel" + *s->name + " extends " + domain + "fbe.FieldModel");
    WriteLineIndent("{");
    Indent(1);

    // Generate struct field model accessors
    if (s->base && !s->base->empty())
        WriteLineIndent("public final " + ConvertBaseFieldName(domain, *s->base, false) + " parent;");
    if (s->body)
        for (const auto& field : s->body->fields)
            WriteLineIndent("public final " + ConvertTypeFieldDeclaration(domain, *field, false) + " " + *field->name + ";");

    // Generate struct field model constructor
    WriteLine();
    WriteLineIndent("public FieldModel" + *s->name + "(" + domain + "fbe.Buffer buffer, long offset)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("super(buffer, offset);");
    std::string prev_offset("4");
    std::string prev_size("4");
    if (s->base && !s->base->empty())
    {
        WriteLineIndent("parent = new " + ConvertBaseFieldName(domain, *s->base, false) + "(buffer, " + prev_offset + " + " + prev_size + ");");
        prev_offset = "parent.fbeOffset()";
        prev_size = "parent.fbeBody() - 4 - 4";
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            WriteLineIndent(*field->name + " = " + ConvertTypeFieldInitialization(domain, *field, prev_offset + " + " + prev_size, false) + ";");
            prev_offset = *field->name + ".fbeOffset()";
            prev_size = *field->name + ".fbeSize()";
        }
    }
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model FBE properties
    WriteLine();
    WriteLineIndent("// Get the field size");
    WriteLineIndent("@Override");
    WriteLineIndent("public long fbeSize() { return 4; }");
    WriteLineIndent("// Get the field body size");
    WriteLineIndent("public long fbeBody()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("long fbeResult = 4 + 4");
    Indent(1);
    if (s->base && !s->base->empty())
        WriteLineIndent("+ parent.fbeBody() - 4 - 4");
    if (s->body)
        for (const auto& field : s->body->fields)
            WriteLineIndent("+ " + *field->name + ".fbeSize()");
    WriteLineIndent(";");
    Indent(-1);
    WriteLineIndent("return fbeResult;");
    Indent(-1);
    WriteLineIndent("}");
    WriteLineIndent("// Get the field extra size");
    WriteLineIndent("@Override");
    WriteLineIndent("public long fbeExtra()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())");
    Indent(1);
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("int fbeStructOffset = readInt32(fbeOffset());");
    WriteLineIndent("if ((fbeStructOffset == 0) || ((_buffer.getOffset() + fbeStructOffset + 4) > _buffer.getSize()))");
    Indent(1);
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("_buffer.shift(fbeStructOffset);");
    WriteLine();
    WriteLineIndent("long fbeResult = fbeBody()");
    Indent(1);
    if (s->base && !s->base->empty())
        WriteLineIndent("+ parent.fbeExtra()");
    if (s->body)
        for (const auto& field : s->body->fields)
            WriteLineIndent("+ " + *field->name + ".fbeExtra()");
    WriteLineIndent(";");
    Indent(-1);
    WriteLine();
    WriteLineIndent("_buffer.unshift(fbeStructOffset);");
    WriteLine();
    WriteLineIndent("return fbeResult;");
    Indent(-1);
    WriteLineIndent("}");
    WriteLineIndent("// Get the field type");
    if (s->base && !s->base->empty() && (s->type == 0))
        WriteLineIndent("public static final long fbeTypeConst = " + ConvertBaseFieldName(domain, *s->base, false) + ".fbeTypeConst;");
    else
        WriteLineIndent("public static final long fbeTypeConst = " + std::to_string(s->type) + ";");
    WriteLineIndent("public long fbeType() { return fbeTypeConst; }");

    // Generate struct field model verify() methods
    WriteLine();
    WriteLineIndent("// Check if the struct value is valid");
    WriteLineIndent("@Override");
    WriteLineIndent("public boolean verify() { return verify(true); }");
    WriteLineIndent("public boolean verify(boolean fbeVerifyType)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())");
    Indent(1);
    WriteLineIndent("return true;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("int fbeStructOffset = readInt32(fbeOffset());");
    WriteLineIndent("if ((fbeStructOffset == 0) || ((_buffer.getOffset() + fbeStructOffset + 4 + 4) > _buffer.getSize()))");
    Indent(1);
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("int fbeStructSize = readInt32(fbeStructOffset);");
    WriteLineIndent("if (fbeStructSize < (4 + 4))");
    Indent(1);
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("int fbeStructType = readInt32(fbeStructOffset + 4);");
    WriteLineIndent("if (fbeVerifyType && (fbeStructType != fbeType()))");
    Indent(1);
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("_buffer.shift(fbeStructOffset);");
    WriteLineIndent("boolean fbeResult = verifyFields(fbeStructSize);");
    WriteLineIndent("_buffer.unshift(fbeStructOffset);");
    WriteLineIndent("return fbeResult;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model verifyFields() method
    WriteLine();
    WriteLineIndent("// Check if the struct fields are valid");
    WriteLineIndent("public boolean verifyFields(long fbeStructSize)");
    WriteLineIndent("{");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("long fbeCurrentSize = 4 + 4;");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("if ((fbeCurrentSize + parent.fbeBody() - 4 - 4) > fbeStructSize)");
            Indent(1);
            WriteLineIndent("return true;");
            Indent(-1);
            WriteLineIndent("if (!parent.verifyFields(fbeStructSize))");
            Indent(1);
            WriteLineIndent("return false;");
            Indent(-1);
            WriteLineIndent("fbeCurrentSize += parent.fbeBody() - 4 - 4;");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent("if ((fbeCurrentSize + " + *field->name + ".fbeSize()) > fbeStructSize)");
                Indent(1);
                WriteLineIndent("return true;");
                Indent(-1);
                WriteLineIndent("if (!" + *field->name + ".verify())");
                Indent(1);
                WriteLineIndent("return false;");
                Indent(-1);
                WriteLineIndent("fbeCurrentSize += " + *field->name + ".fbeSize();");
            }
        }
        WriteLine();
    }
    WriteLineIndent("return true;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model getBegin() method
    WriteLine();
    WriteLineIndent("// Get the struct value (begin phase)");
    WriteLineIndent("public long getBegin()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())");
    Indent(1);
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("int fbeStructOffset = readInt32(fbeOffset());");
    WriteLineIndent("assert ((fbeStructOffset > 0) && ((_buffer.getOffset() + fbeStructOffset + 4 + 4) <= _buffer.getSize())) : \"Model is broken!\";");
    WriteLineIndent("if ((fbeStructOffset == 0) || ((_buffer.getOffset() + fbeStructOffset + 4 + 4) > _buffer.getSize()))");
    Indent(1);
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("int fbeStructSize = readInt32(fbeStructOffset);");
    WriteLineIndent("assert (fbeStructSize >= (4 + 4)) : \"Model is broken!\";");
    WriteLineIndent("if (fbeStructSize < (4 + 4))");
    Indent(1);
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("_buffer.shift(fbeStructOffset);");
    WriteLineIndent("return fbeStructOffset;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model getEnd() method
    WriteLine();
    WriteLineIndent("// Get the struct value (end phase)");
    WriteLineIndent("public void getEnd(long fbeBegin)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("_buffer.unshift(fbeBegin);");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model get() methods
    WriteLine();
    WriteLineIndent("// Get the struct value");
    WriteLineIndent("public " + struct_name + " get() { return get(new " + struct_name + "()); }");
    WriteLineIndent("public " + struct_name + " get(" + struct_name + " fbeValue)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("long fbeBegin = getBegin();");
    WriteLineIndent("if (fbeBegin == 0)");
    Indent(1);
    WriteLineIndent("return fbeValue;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("int fbeStructSize = readInt32(0);");
    WriteLineIndent("getFields(fbeValue, fbeStructSize);");
    WriteLineIndent("getEnd(fbeBegin);");
    WriteLineIndent("return fbeValue;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model getFields() method
    WriteLine();
    WriteLineIndent("// Get the struct fields values");
    WriteLineIndent("public void getFields(" + struct_name + " fbeValue, long fbeStructSize)");
    WriteLineIndent("{");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("long fbeCurrentSize = 4 + 4;");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("if ((fbeCurrentSize + parent.fbeBody() - 4 - 4) <= fbeStructSize)");
            Indent(1);
            WriteLineIndent("parent.getFields(fbeValue, fbeStructSize);");
            Indent(-1);
            WriteLineIndent("fbeCurrentSize += parent.fbeBody() - 4 - 4;");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent("if ((fbeCurrentSize + " + *field->name + ".fbeSize()) <= fbeStructSize)");
                Indent(1);
                if (field->array || field->vector || field->list || field->set || field->map || field->hash)
                    WriteLineIndent(*field->name + ".get(fbeValue." + *field->name + ");");
                else
                    WriteLineIndent("fbeValue." + *field->name + " = " + *field->name + ".get(" + (field->value ? ConvertConstant(domain, package, *field->type, *field->value, field->optional) : "") + ");");
                Indent(-1);
                WriteLineIndent("else");
                Indent(1);
                if (field->vector || field->list || field->set || field->map || field->hash)
                    WriteLineIndent("fbeValue." + *field->name + ".clear();");
                else
                    WriteLineIndent("fbeValue." + *field->name + " = " + ConvertDefault(domain, package, *field) + ";");
                Indent(-1);
                WriteLineIndent("fbeCurrentSize += " + *field->name + ".fbeSize();");
            }
        }
    }
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model setBegin() method
    WriteLine();
    WriteLineIndent("// Set the struct value (begin phase)");
    WriteLineIndent("public long setBegin()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : \"Model is broken!\";");
    WriteLineIndent("if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())");
    Indent(1);
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("int fbeStructSize = (int)fbeBody();");
    WriteLineIndent("int fbeStructOffset = (int)(_buffer.allocate(fbeStructSize) - _buffer.getOffset());");
    WriteLineIndent("assert ((fbeStructOffset > 0) && ((_buffer.getOffset() + fbeStructOffset + fbeStructSize) <= _buffer.getSize())) : \"Model is broken!\";");
    WriteLineIndent("if ((fbeStructOffset <= 0) || ((_buffer.getOffset() + fbeStructOffset + fbeStructSize) > _buffer.getSize()))");
    Indent(1);
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("write(fbeOffset(), fbeStructOffset);");
    WriteLineIndent("write(fbeStructOffset, fbeStructSize);");
    WriteLineIndent("write(fbeStructOffset + 4, (int)fbeType());");
    WriteLine();
    WriteLineIndent("_buffer.shift(fbeStructOffset);");
    WriteLineIndent("return fbeStructOffset;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model setEnd() method
    WriteLine();
    WriteLineIndent("// Set the struct value (end phase)");
    WriteLineIndent("public void setEnd(long fbeBegin)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("_buffer.unshift(fbeBegin);");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model set() method
    WriteLine();
    WriteLineIndent("// Set the struct value");
    WriteLineIndent("public void set(" + struct_name + " fbeValue)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("long fbeBegin = setBegin();");
    WriteLineIndent("if (fbeBegin == 0)");
    Indent(1);
    WriteLineIndent("return;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("setFields(fbeValue);");
    WriteLineIndent("setEnd(fbeBegin);");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model setFields() method
    WriteLine();
    WriteLineIndent("// Set the struct fields values");
    WriteLineIndent("public void setFields(" + struct_name + " fbeValue)");
    WriteLineIndent("{");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        if (s->base && !s->base->empty())
            WriteLineIndent("parent.setFields(fbeValue);");
        if (s->body)
            for (const auto& field : s->body->fields)
                WriteLineIndent(*field->name + ".set(fbeValue." + *field->name + ");");
    }
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model end
    Indent(-1);
    WriteLineIndent("}");

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateStructModel(const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";
    std::string package = *p->name;
    std::string struct_name = domain + package + "." + *s->name;

    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the file
    CppCommon::Path file = path / (*s->name + "Model.java");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    // Generate struct model begin
    WriteLine();
    WriteLineIndent("// Fast Binary Encoding " + *s->name + " model");
    WriteLineIndent("public final class " + *s->name + "Model extends " + domain + "fbe." + "Model");
    WriteLineIndent("{");
    Indent(1);

    // Generate struct model accessor
    WriteLineIndent("public final FieldModel" + *s->name + " model;");

    // Generate struct model constructors
    WriteLine();
    WriteLineIndent("public " + *s->name + "Model() { model = new FieldModel" + *s->name + "(getBuffer(), 4); }");
    WriteLineIndent("public " + *s->name + "Model(" + domain + "fbe.Buffer buffer) { super(buffer); model = new FieldModel" + *s->name + "(getBuffer(), 4); }");

    // Generate struct model FBE properties
    WriteLine();
    WriteLineIndent("// Get the model size");
    WriteLineIndent("public long fbeSize() { return model.fbeSize() + model.fbeExtra(); }");
    WriteLineIndent("// Get the model type");
    WriteLineIndent("public static final long fbeTypeConst = FieldModel" + *s->name + ".fbeTypeConst;");
    WriteLineIndent("public long fbeType() { return fbeTypeConst; }");

    // Generate struct model verify() method
    WriteLine();
    WriteLineIndent("// Check if the struct value is valid");
    WriteLineIndent("public boolean verify()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if ((getBuffer().getOffset() + model.fbeOffset() - 4) > getBuffer().getSize())");
    Indent(1);
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("int fbeFullSize = readInt32(model.fbeOffset() - 4);");
    WriteLineIndent("if (fbeFullSize < model.fbeSize())");
    Indent(1);
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("return model.verify();");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model createBegin() method
    WriteLine();
    WriteLineIndent("// Create a new model (begin phase)");
    WriteLineIndent("public long createBegin()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("long fbeBegin = getBuffer().allocate(4 + model.fbeSize());");
    WriteLineIndent("return fbeBegin;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model createEnd() method
    WriteLine();
    WriteLineIndent("// Create a new model (end phase)");
    WriteLineIndent("public long createEnd(long fbeBegin)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("long fbeEnd = getBuffer().getSize();");
    WriteLineIndent("int fbeFullSize = (int)(fbeEnd - fbeBegin);");
    WriteLineIndent("write(model.fbeOffset() - 4, fbeFullSize);");
    WriteLineIndent("return fbeFullSize;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model serialize() method
    WriteLine();
    WriteLineIndent("// Serialize the struct value");
    WriteLineIndent("public long serialize(" + struct_name + " value)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("long fbeBegin = createBegin();");
    WriteLineIndent("model.set(value);");
    WriteLineIndent("long fbeFullSize = createEnd(fbeBegin);");
    WriteLineIndent("return fbeFullSize;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model deserialize() methods
    WriteLine();
    WriteLineIndent("// Deserialize the struct value");
    WriteLineIndent("public " + struct_name + " deserialize() { var value = new " + struct_name + "(); deserialize(value); return value; }");
    WriteLineIndent("public long deserialize(" + struct_name + " value)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if ((getBuffer().getOffset() + model.fbeOffset() - 4) > getBuffer().getSize())");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("value = new " + struct_name + "();");
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("int fbeFullSize = readInt32(model.fbeOffset() - 4);");
    WriteLineIndent("assert (fbeFullSize >= model.fbeSize()) : \"Model is broken!\";");
    WriteLineIndent("if (fbeFullSize < model.fbeSize())");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("value = new " + struct_name + "();");
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("value = model.get(value);");
    WriteLineIndent("return fbeFullSize;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model next() method
    WriteLine();
    WriteLineIndent("// Move to the next struct value");
    WriteLineIndent("public void next(long prev)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("model.fbeShift(prev);");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model end
    Indent(-1);
    WriteLineIndent("}");

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateStructFinalModel(const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";
    std::string package = *p->name;
    std::string struct_name = domain + *p->name + "." + *s->name;

    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the file
    CppCommon::Path file = path / ("FinalModel" + *s->name + ".java");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    // Generate struct final model begin
    WriteLine();
    WriteLineIndent("// Fast Binary Encoding " + *s->name + " final model");
    WriteLineIndent("public final class FinalModel" + *s->name + " extends " + domain + "fbe." + "FinalModel");
    WriteLineIndent("{");
    Indent(1);

    // Generate struct final model accessors
    if (s->base && !s->base->empty())
        WriteLineIndent("public final " + ConvertBaseFieldName(domain, *s->base, true) + " parent;");
    if (s->body)
        for (const auto& field : s->body->fields)
            WriteLineIndent("public final " + ConvertTypeFieldDeclaration(domain, *field, true) + " " + *field->name + ";");

    // Generate struct final model constructor
    WriteLine();
    WriteLineIndent("public FinalModel" + *s->name + "(" + domain + "fbe.Buffer buffer, long offset)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("super(buffer, offset);");
    if (s->base && !s->base->empty())
        WriteLineIndent("parent = new " + ConvertBaseFieldName(domain, *s->base, true) + "(buffer, 0);");
    if (s->body)
        for (const auto& field : s->body->fields)
            WriteLineIndent(*field->name + " = " + ConvertTypeFieldInitialization(domain, *field, "0", true) + ";");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct final model FBE properties
    WriteLine();
    WriteLineIndent("// Get the allocation size");
    WriteLineIndent("public long fbeAllocationSize(" + struct_name + " fbeValue)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("long fbeResult = 0");
    Indent(1);
    if (s->base && !s->base->empty())
        WriteLineIndent("+ parent.fbeAllocationSize(fbeValue)");
    if (s->body)
        for (const auto& field : s->body->fields)
            WriteLineIndent("+ " + *field->name + ".fbeAllocationSize(fbeValue." + *field->name + ")");
    WriteLineIndent(";");
    Indent(-1);
    WriteLineIndent("return fbeResult;");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("// Get the final type");
    if (s->base && !s->base->empty() && (s->type == 0))
        WriteLineIndent("public static final long fbeTypeConst = " + ConvertBaseFieldName(domain, *s->base, true) + ".fbeTypeConst;");
    else
        WriteLineIndent("public static final long fbeTypeConst = " + std::to_string(s->type) + ";");
    WriteLineIndent("public long fbeType() { return fbeTypeConst; }");

    // Generate struct final model verify() methods
    WriteLine();
    WriteLineIndent("// Check if the struct value is valid");
    WriteLineIndent("@Override");
    WriteLineIndent("public long verify()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("_buffer.shift(fbeOffset());");
    WriteLineIndent("long fbeResult = verifyFields();");
    WriteLineIndent("_buffer.unshift(fbeOffset());");
    WriteLineIndent("return fbeResult;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct final model verifyFields() method
    WriteLine();
    WriteLineIndent("// Check if the struct fields are valid");
    WriteLineIndent("public long verifyFields()");
    WriteLineIndent("{");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("long fbeCurrentOffset = 0;");
        WriteLineIndent("long fbeFieldSize = 0;");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("parent.fbeOffset(fbeCurrentOffset);");
            WriteLineIndent("fbeFieldSize = parent.verifyFields();");
            WriteLineIndent("if (fbeFieldSize == Long.MAX_VALUE)");
            Indent(1);
            WriteLineIndent("return Long.MAX_VALUE;");
            Indent(-1);
            WriteLineIndent("fbeCurrentOffset += fbeFieldSize;");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent(*field->name + ".fbeOffset(fbeCurrentOffset);");
                WriteLineIndent("fbeFieldSize = " + *field->name + ".verify();");
                WriteLineIndent("if (fbeFieldSize == Long.MAX_VALUE)");
                Indent(1);
                WriteLineIndent("return Long.MAX_VALUE;");
                Indent(-1);
                WriteLineIndent("fbeCurrentOffset += fbeFieldSize;");
            }
        }
        WriteLine();
        WriteLineIndent("return fbeCurrentOffset;");
    }
    else
        WriteLineIndent("return 0;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct final model get() methods
    WriteLine();
    WriteLineIndent("// Get the struct value");
    WriteLineIndent("public " + struct_name + " get(" + domain + "fbe.Size fbeSize) { return get(fbeSize, new " + struct_name + "()); }");
    WriteLineIndent("public " + struct_name + " get(" + domain + "fbe.Size fbeSize, " + struct_name + " fbeValue)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("_buffer.shift(fbeOffset());");
    WriteLineIndent("fbeSize.value = getFields(fbeValue);");
    WriteLineIndent("_buffer.unshift(fbeOffset());");
    WriteLineIndent("return fbeValue;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct final model getFields() method
    WriteLine();
    WriteLineIndent("// Get the struct fields values");
    WriteLineIndent("public long getFields(" + struct_name + " fbeValue)");
    WriteLineIndent("{");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("long fbeCurrentOffset = 0;");
        WriteLineIndent("long fbeCurrentSize = 0;");
        WriteLineIndent("var fbeFieldSize = new " + domain + "fbe.Size(0);");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("parent.fbeOffset(fbeCurrentOffset);");
            WriteLineIndent("fbeFieldSize.value = parent.getFields(fbeValue);");
            WriteLineIndent("fbeCurrentOffset += fbeFieldSize.value;");
            WriteLineIndent("fbeCurrentSize += fbeFieldSize.value;");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent(*field->name + ".fbeOffset(fbeCurrentOffset);");
                if (field->array || field->vector || field->list || field->set || field->map || field->hash)
                    WriteLineIndent("fbeFieldSize.value = " + *field->name + ".get(fbeValue." + *field->name + ");");
                else
                    WriteLineIndent("fbeValue." + *field->name + " = " + *field->name + ".get(fbeFieldSize);");
                WriteLineIndent("fbeCurrentOffset += fbeFieldSize.value;");
                WriteLineIndent("fbeCurrentSize += fbeFieldSize.value;");
            }
        }
        WriteLine();
        WriteLineIndent("return fbeCurrentSize;");
    }
    else
        WriteLineIndent("return 0;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct final model set() method
    WriteLine();
    WriteLineIndent("// Set the struct value");
    WriteLineIndent("public long set(" + struct_name + " fbeValue)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("_buffer.shift(fbeOffset());");
    WriteLineIndent("long fbeSize = setFields(fbeValue);");
    WriteLineIndent("_buffer.unshift(fbeOffset());");
    WriteLineIndent("return fbeSize;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct final model setFields() method
    WriteLine();
    WriteLineIndent("// Set the struct fields values");
    WriteLineIndent("public long setFields(" + struct_name + " fbeValue)");
    WriteLineIndent("{");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("long fbeCurrentOffset = 0;");
        WriteLineIndent("long fbeCurrentSize = 0;");
        WriteLineIndent("var fbeFieldSize = new " + domain + "fbe.Size();");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("parent.fbeOffset(fbeCurrentOffset);");
            WriteLineIndent("fbeFieldSize.value = parent.setFields(fbeValue);");
            WriteLineIndent("fbeCurrentOffset += fbeFieldSize.value;");
            WriteLineIndent("fbeCurrentSize += fbeFieldSize.value;");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent(*field->name + ".fbeOffset(fbeCurrentOffset);");
                WriteLineIndent("fbeFieldSize.value = " + *field->name + ".set(fbeValue." + *field->name + ");");
                WriteLineIndent("fbeCurrentOffset += fbeFieldSize.value;");
                WriteLineIndent("fbeCurrentSize += fbeFieldSize.value;");
            }
        }
        WriteLine();
        WriteLineIndent("return fbeCurrentSize;");
    }
    else
        WriteLineIndent("return 0;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct final model end
    Indent(-1);
    WriteLineIndent("}");

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateStructModelFinal(const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";
    std::string package = *p->name;
    std::string struct_name = domain + package + "." + *s->name;

    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the file
    CppCommon::Path file = path / (*s->name + "FinalModel.java");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    // Generate struct model final begin
    WriteLine();
    WriteLineIndent("// Fast Binary Encoding " + *s->name + " final model");
    WriteLineIndent("public final class " + *s->name + "FinalModel extends " + domain + "fbe." + "Model");
    WriteLineIndent("{");
    Indent(1);

    // Generate struct model final accessor
    WriteLineIndent("private final FinalModel" + *s->name + " _model;");

    // Generate struct model final constructors
    WriteLine();
    WriteLineIndent("public " + *s->name + "FinalModel() { _model = new FinalModel" + *s->name + "(getBuffer(), 8); }");
    WriteLineIndent("public " + *s->name + "FinalModel(" + domain + "fbe.Buffer buffer) { super(buffer); _model = new FinalModel" + *s->name + "(getBuffer(), 8); }");

    // Generate struct model final FBE properties
    WriteLine();
    WriteLineIndent("// Get the model type");
    WriteLineIndent("public static final long fbeTypeConst = FinalModel" + *s->name + ".fbeTypeConst;");
    WriteLineIndent("public long fbeType() { return fbeTypeConst; }");

    // Generate struct model final verify() method
    WriteLine();
    WriteLineIndent("// Check if the struct value is valid");
    WriteLineIndent("public boolean verify()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if ((getBuffer().getOffset() + _model.fbeOffset()) > getBuffer().getSize())");
    Indent(1);
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("int fbeStructSize = readInt32(_model.fbeOffset() - 8);");
    WriteLineIndent("int fbeStructType = readInt32(_model.fbeOffset() - 4);");
    WriteLineIndent("if ((fbeStructSize <= 0) || (fbeStructType != fbeType()))");
    Indent(1);
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("return ((8 + _model.verify()) == fbeStructSize);");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model final serialize() method
    WriteLine();
    WriteLineIndent("// Serialize the struct value");
    WriteLineIndent("public long serialize(" + struct_name + " value)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("long fbeInitialSize = getBuffer().getSize();");
    WriteLine();
    WriteLineIndent("int fbeStructType = (int)fbeType();");
    WriteLineIndent("int fbeStructSize = (int)(8 + _model.fbeAllocationSize(value));");
    WriteLineIndent("int fbeStructOffset = (int)(getBuffer().allocate(fbeStructSize) - getBuffer().getOffset());");
    WriteLineIndent("assert ((getBuffer().getOffset() + fbeStructOffset + fbeStructSize) <= getBuffer().getSize()) : \"Model is broken!\";");
    WriteLineIndent("if ((getBuffer().getOffset() + fbeStructOffset + fbeStructSize) > getBuffer().getSize())");
    Indent(1);
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("fbeStructSize = (int)(8 + _model.set(value));");
    WriteLineIndent("getBuffer().resize(fbeInitialSize + fbeStructSize);");
    WriteLine();
    WriteLineIndent("write(_model.fbeOffset() - 8, fbeStructSize);");
    WriteLineIndent("write(_model.fbeOffset() - 4, fbeStructType);");
    WriteLine();
    WriteLineIndent("return fbeStructSize;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model final deserialize() methods
    WriteLine();
    WriteLineIndent("// Deserialize the struct value");
    WriteLineIndent("public " + struct_name + " deserialize() { var value = new " + struct_name + "(); deserialize(value); return value; }");
    WriteLineIndent("public long deserialize(" + struct_name + " value)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("assert ((getBuffer().getOffset() + _model.fbeOffset()) <= getBuffer().getSize()) : \"Model is broken!\";");
    WriteLineIndent("if ((getBuffer().getOffset() + _model.fbeOffset()) > getBuffer().getSize())");
    Indent(1);
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("long fbeStructSize = readInt32(_model.fbeOffset() - 8);");
    WriteLineIndent("long fbeStructType = readInt32(_model.fbeOffset() - 4);");
    WriteLineIndent("assert ((fbeStructSize > 0) && (fbeStructType == fbeType())) : \"Model is broken!\";");
    WriteLineIndent("if ((fbeStructSize <= 0) || (fbeStructType != fbeType()))");
    Indent(1);
    WriteLineIndent("return 8;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("var fbeSize = new " + domain + "fbe.Size();");
    WriteLineIndent("value = _model.get(fbeSize, value);");
    WriteLineIndent("return 8 + fbeSize.value;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model final next() method
    WriteLine();
    WriteLineIndent("// Move to the next struct value");
    WriteLineIndent("public void next(long prev)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("_model.fbeShift(prev);");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model final end
    Indent(-1);
    WriteLineIndent("}");

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateProtocolVersion(const std::shared_ptr<Package>& p)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";
    std::string package = *p->name;

    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the file
    CppCommon::Path file = path / ("ProtocolVersion.java");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    // Generate protocol version class
    WriteLine();
    WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " protocol version");
    WriteLineIndent("public final class ProtocolVersion");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("// Protocol major version");
    WriteLineIndent("public static final int Major = " + std::to_string(p->version->major) + ";");
    WriteLineIndent("// Protocol minor version");
    WriteLineIndent("public static final int Minor = " + std::to_string(p->version->minor) + ";");
    Indent(-1);
    WriteLineIndent("}");

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateSender(const std::shared_ptr<Package>& p, bool final)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";
    std::string package = *p->name;

    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    std::string sender = (final ? "FinalSender" : "Sender");
    std::string model = (final ? "FinalModel" : "Model");

    // Generate the file
    CppCommon::Path file = path / (sender + ".java");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    // Generate sender begin
    WriteLine();
    if (final)
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " final sender");
    else
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " sender");
    WriteLineIndent("public class " + sender + " extends " + domain + "fbe.Sender");
    WriteLineIndent("{");
    Indent(1);

    // Generate imported senders accessors
    if (p->import)
    {
        WriteLineIndent("// Imported senders");
        for (const auto& import : p->import->imports)
            WriteLineIndent("public final " + domain + *import + ".fbe." + sender + " " + *import + "Sender;");
        WriteLine();
    }

    // Generate sender models accessors
    if (p->body)
    {
        WriteLineIndent("// Sender models accessors");
        for (const auto& s : p->body->structs)
            if (s->message)
                WriteLineIndent("public final " + *s->name + model + " " + *s->name + "Model;");
        WriteLine();
    }

    // Generate sender constructors
    WriteLineIndent("public " + sender + "()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("super(" + std::string(final ? "true" : "false") + ");");
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent(*import + "Sender = new " + domain + *import + ".fbe." + sender + "(getBuffer());");
    }
    if (p->body)
    {
        for (const auto& s : p->body->structs)
            if (s->message)
                WriteLineIndent(*s->name + "Model = new " + *s->name + model + "(getBuffer());");
    }
    Indent(-1);
    WriteLineIndent("}");
    WriteLineIndent("public " + sender + "(" + domain + "fbe.Buffer buffer)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("super(buffer, " + std::string(final ? "true" : "false") + ");");
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent(*import + "Sender = new " + domain + *import + ".fbe." + sender + "(getBuffer());");
    }
    if (p->body)
    {
        for (const auto& s : p->body->structs)
            if (s->message)
                WriteLineIndent(*s->name + "Model = new " + *s->name + model + "(getBuffer());");
    }
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    // Generate generic sender method
    WriteLineIndent("public long send(Object obj)");
    WriteLineIndent("{");
    Indent(1);
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_name = domain + *p->name + "." + *s->name;
                WriteLineIndent("if (obj instanceof " + struct_name + ")");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent(struct_name + " value = (" + struct_name + ")obj;");
                WriteLineIndent("if (value.fbeType() == " + *s->name + "Model.fbeType())");
                Indent(1);
                WriteLineIndent("return send(value);");
                Indent(-1);
                Indent(-1);
                WriteLineIndent("}");
            }
        }
    }
    WriteLine();
    if (p->import)
    {
        WriteLineIndent("// Try to send using imported senders");
        WriteLineIndent("long result = 0;");
        for (const auto& import : p->import->imports)
        {
            WriteLineIndent("result = " + *import + "Sender.send(obj);");
            WriteLineIndent("if (result > 0)");
            Indent(1);
            WriteLineIndent("return result;");
            Indent(-1);
        }
        WriteLine();
    }
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    // Generate sender methods
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_name = domain + *p->name + "." + *s->name;
                WriteLineIndent("public long send(" + struct_name + " value)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("// Serialize the value into the FBE stream");
                WriteLineIndent("long serialized = " + *s->name + "Model.serialize(value);");
                WriteLineIndent("assert (serialized > 0) : \"" + struct_name + " serialization failed!\";");
                WriteLineIndent("assert " + *s->name + "Model.verify() : \"" + struct_name + " validation failed!\";");
                WriteLine();
                WriteLineIndent("// Log the value");
                WriteLineIndent("if (getLogging())");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("String message = value.toString();");
                WriteLineIndent("onSendLog(message);");
                Indent(-1);
                WriteLineIndent("}");
                WriteLine();
                WriteLineIndent("// Send the serialized value");
                WriteLineIndent("return sendSerialized(serialized);");
                Indent(-1);
                WriteLineIndent("}");
            }
        }
    }

    // Generate sender message handler
    WriteLine();
    WriteLineIndent("// Send message handler");
    WriteLineIndent("@Override");
    WriteLineIndent("protected long onSend(byte[] buffer, long offset, long size) { throw new UnsupportedOperationException(\"" + domain + *p->name + ".fbe.Sender.onSend() not implemented!\"); }");

    // Generate sender end
    Indent(-1);
    WriteLineIndent("}");

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateReceiver(const std::shared_ptr<Package>& p, bool final)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";
    std::string package = *p->name;

    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    std::string receiver = (final ? "FinalReceiver" : "Receiver");
    std::string model = (final ? "FinalModel" : "Model");

    // Generate the file
    CppCommon::Path file = path / (receiver + ".java");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    // Generate receiver begin
    WriteLine();
    if (final)
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " final receiver");
    else
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " receiver");
    WriteLineIndent("public class " + receiver + " extends " + domain + "fbe.Receiver");
    WriteLineIndent("{");
    Indent(1);

    // Generate imported receivers accessors
    if (p->import)
    {
        WriteLineIndent("// Imported receivers");
        for (const auto& import : p->import->imports)
            WriteLineIndent("public " + domain + *import + ".fbe." + receiver + " " + *import + "Receiver;");
        WriteLine();
    }

    // Generate receiver models accessors
    if (p->body)
    {
        WriteLineIndent("// Receiver values accessors");
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_name = domain + *p->name + "." + *s->name;
                WriteLineIndent("private final " + struct_name + " " + *s->name + "Value;");
            }
        }
        WriteLine();
        WriteLineIndent("// Receiver models accessors");
        for (const auto& s : p->body->structs)
            if (s->message)
                WriteLineIndent("private final " + *s->name + model + " " + *s->name + "Model;");
        WriteLine();
    }

    // Generate receiver constructors
    WriteLineIndent("public " + receiver + "()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("super(" + std::string(final ? "true" : "false") + ");");
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent(*import + "Receiver = new " + domain + *import + ".fbe." + receiver + "(getBuffer());");
    }
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_name = domain + *p->name + "." + *s->name;
                WriteLineIndent(*s->name + "Value = new " + struct_name + "();");
                WriteLineIndent(*s->name + "Model = new " + *s->name + model + "();");
            }
        }
    }
    Indent(-1);
    WriteLineIndent("}");
    WriteLineIndent("public " + receiver + "(" + domain + "fbe.Buffer buffer)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("super(buffer, " + std::string(final ? "true" : "false") + ");");
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent(*import + "Receiver = new " + domain + *import + ".fbe." + receiver + "(getBuffer());");
    }
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_name = domain + *p->name + "." + *s->name;
                WriteLineIndent(*s->name + "Value = new " + struct_name + "();");
                WriteLineIndent(*s->name + "Model = new " + *s->name + model + "();");
            }
        }
    }
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    // Generate receiver handlers
    if (p->body)
    {
        WriteLineIndent("// Receive handlers");
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_name = domain + *p->name + "." + *s->name;
                WriteLineIndent("protected void onReceive(" + struct_name + " value) {}");
            }
        }
        WriteLine();
    }

    // Generate receiver message handler
    WriteLineIndent("@Override");
    WriteLineIndent("public boolean onReceive(long type, byte[] buffer, long offset, long size)");
    WriteLineIndent("{");
    Indent(1);
    if (p->body)
    {
        WriteLineIndent("switch ((int)type)");
        WriteLineIndent("{");
        Indent(1);
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLineIndent("case (int)" + domain + package + ".fbe." + *s->name + model + ".fbeTypeConst:");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("// Deserialize the value from the FBE stream");
                WriteLineIndent(*s->name + "Model.attach(buffer, offset);");
                WriteLineIndent("assert " + *s->name + "Model.verify() : \"" + *p->name + "." + *s->name + " validation failed!\";");
                WriteLineIndent("long deserialized = " + *s->name + "Model.deserialize(" + *s->name + "Value);");
                WriteLineIndent("assert (deserialized > 0) : \"" + *p->name + "." + *s->name + " deserialization failed!\";");
                WriteLine();
                WriteLineIndent("// Log the value");
                WriteLineIndent("if (getLogging())");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("String message = " + *s->name + "Value.toString();");
                WriteLineIndent("onReceiveLog(message);");
                Indent(-1);
                WriteLineIndent("}");
                WriteLine();
                WriteLineIndent("// Call receive handler with deserialized value");
                WriteLineIndent("onReceive(" + *s->name + "Value);");
                WriteLineIndent("return true;");
                Indent(-1);
                WriteLineIndent("}");
            }
        }
        WriteLineIndent("default: break;");
        Indent(-1);
        WriteLineIndent("}");
    }
    if (p->import)
    {
        WriteLine();
        for (const auto& import : p->import->imports)
        {
            WriteLineIndent("if ((" + *import + "Receiver != null) && " + *import + "Receiver.onReceive(type, buffer, offset, size))");
            Indent(1);
            WriteLineIndent("return true;");
            Indent(-1);
        }
    }
    WriteLine();
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate receiver end
    Indent(-1);
    WriteLineIndent("}");

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateProxy(const std::shared_ptr<Package>& p, bool final)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";
    std::string package = *p->name;

    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    std::string proxy = (final ? "FinalProxy" : "Proxy");
    std::string model = (final ? "FinalModel" : "Model");

    // Generate the file
    CppCommon::Path file = path / (proxy + ".java");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    // Generate proxy begin
    WriteLine();
    if (final)
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " final proxy");
    else
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " proxy");
    WriteLineIndent("public class " + proxy + " extends " + domain + "fbe.Receiver");
    WriteLineIndent("{");
    Indent(1);

    // Generate imported proxy accessors
    if (p->import)
    {
        WriteLineIndent("// Imported proxy");
        for (const auto& import : p->import->imports)
            WriteLineIndent("public " + domain + *import + ".fbe." + proxy + " " + *import + "Proxy;");
        WriteLine();
    }

    // Generate proxy models accessors
    if (p->body)
    {
        WriteLineIndent("// Proxy models accessors");
        for (const auto& s : p->body->structs)
            if (s->message)
                WriteLineIndent("private final " + *s->name + model + " " + *s->name + "Model;");
        WriteLine();
    }

    // Generate proxy constructors
    WriteLineIndent("public " + proxy + "()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("super(" + std::string(final ? "true" : "false") + ");");
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent(*import + "Proxy = new " + domain + *import + ".fbe." + proxy + "(getBuffer());");
    }
    if (p->body)
    {
        for (const auto& s : p->body->structs)
            if (s->message)
                WriteLineIndent(*s->name + "Model = new " + *s->name + model + "();");
    }
    Indent(-1);
    WriteLineIndent("}");
    WriteLineIndent("public " + proxy + "(" + domain + "fbe.Buffer buffer)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("super(buffer, " + std::string(final ? "true" : "false") + ");");
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent(*import + "Proxy = new " + domain + *import + ".fbe." + proxy + "(getBuffer());");
    }
    if (p->body)
    {
        for (const auto& s : p->body->structs)
            if (s->message)
                WriteLineIndent(*s->name + "Model = new " + *s->name + model + "();");
    }
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    // Generate proxy handlers
    if (p->body)
    {
        WriteLineIndent("// Proxy handlers");
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_model = *s->name + model;
                WriteLineIndent("protected void onProxy(" + struct_model + " model, long type, byte[] buffer, long offset, long size) {}");
            }
        }
        WriteLine();
    }

    // Generate proxy message handler
    WriteLineIndent("@Override");
    WriteLineIndent("public boolean onReceive(long type, byte[] buffer, long offset, long size)");
    WriteLineIndent("{");
    Indent(1);
    if (p->body)
    {
        WriteLineIndent("switch ((int)type)");
        WriteLineIndent("{");
        Indent(1);
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLineIndent("case (int)" + domain + package + ".fbe." + *s->name + model + ".fbeTypeConst:");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("// Attach the FBE stream to the proxy model");
                WriteLineIndent(*s->name + "Model.attach(buffer, offset);");
                WriteLineIndent("assert " + *s->name + "Model.verify() : \"" + *p->name + "." + *s->name + " validation failed!\";");
                WriteLine();
                WriteLineIndent("long fbeBegin = " + *s->name + "Model.model.getBegin();");
                WriteLineIndent("if (fbeBegin == 0)");
                Indent(1);
                WriteLineIndent("return false;");
                Indent(-1);
                WriteLineIndent("// Call proxy handler");
                WriteLineIndent("onProxy(" + *s->name + "Model, type, buffer, offset, size);");
                WriteLineIndent(*s->name + "Model.model.getEnd(fbeBegin);");
                WriteLineIndent("return true;");
                Indent(-1);
                WriteLineIndent("}");
            }
        }
        WriteLineIndent("default: break;");
        Indent(-1);
        WriteLineIndent("}");
    }
    if (p->import)
    {
        WriteLine();
        for (const auto& import : p->import->imports)
        {
            WriteLineIndent("if ((" + *import + "Proxy != null) && " + *import + "Proxy.onReceive(type, buffer, offset, size))");
            Indent(1);
            WriteLineIndent("return true;");
            Indent(-1);
        }
    }
    WriteLine();
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate proxy end
    Indent(-1);
    WriteLineIndent("}");

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorJava::GenerateJson(const std::shared_ptr<Package>& p)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";
    std::string package = *p->name;

    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the file
    CppCommon::Path file = path / "Json.java";
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    // Generate JSON engine begin
    WriteLine();
    WriteLineIndent("// Fast Binary Encoding " + *p->name + " JSON engine");
    WriteLineIndent("public final class Json");
    WriteLineIndent("{");
    Indent(1);

    WriteLineIndent("private static final com.google.gson.Gson _engine;");
    WriteLine();
    WriteLineIndent("// Get the JSON engine");
    WriteLineIndent("public static com.google.gson.Gson getEngine() { return _engine; }");
    WriteLine();

    // Generate JSON engine static initialization
    WriteLineIndent("static");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("_engine = register(new com.google.gson.GsonBuilder()).create();");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    // Generate JSON engine private constructor
    WriteLineIndent("private Json() {}");
    WriteLine();

    // Generate JSON engine Register() method
    WriteLineIndent("public static com.google.gson.GsonBuilder register(com.google.gson.GsonBuilder builder)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent(domain + "fbe.Json.register(builder);");
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent(domain + *import + ".fbe.Json.register(builder);");
    }
    if (p->body)
    {
        for (const auto& e : p->body->enums)
            WriteLineIndent("builder.registerTypeAdapter(" + domain + package + "." + *e->name + ".class, new " + *e->name + "Json());");
        for (const auto& f : p->body->flags)
            WriteLineIndent("builder.registerTypeAdapter(" + domain + package + "." + *f->name + ".class, new " + *f->name + "Json());");
    }
    WriteLineIndent("return builder;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate JSON engine end
    Indent(-1);
    WriteLineIndent("}");

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

bool GeneratorJava::IsKnownType(const std::string& type)
{
    return ((type == "bool") ||
            (type == "byte") || (type == "bytes") ||
            (type == "char") || (type == "wchar") ||
            (type == "int8") || (type == "uint8") ||
            (type == "int16") || (type == "uint16") ||
            (type == "int32") || (type == "uint32") ||
            (type == "int64") || (type == "uint64") ||
            (type == "float") || (type == "double") ||
            (type == "decimal") || (type == "string") ||
            (type == "timestamp") || (type == "uuid"));
}

bool GeneratorJava::IsImportedType(const std::string& type)
{
    size_t pos = type.find_last_of('.');
    return (pos != std::string::npos);
}

bool GeneratorJava::IsPackageType(const std::string& type)
{
    if (IsKnownType(type) || IsImportedType(type))
        return true;

    return ((type == "Boolean") ||
            (type == "Byte") || (type == "java.nio.ByteBuffer") ||
            (type == "Character") ||
            (type == "Byte") || (type == "UByte") ||
            (type == "Short") || (type == "UShort") ||
            (type == "Integer") || (type == "UInteger") ||
            (type == "Long") || (type == "ULong") ||
            (type == "Float") || (type == "Double") ||
            (type == "java.math.BigDecimal") || (type == "java.math.BigInteger") ||
            (type == "String") || (type == "java.util.Date") || (type == "java.time.Instant") || (type == "java.util.UUID"));
}

bool GeneratorJava::IsPrimitiveType(const std::string& type, bool optional)
{
    if (optional)
        return false;

    return ((type == "bool") || (type == "byte") ||
            (type == "char") || (type == "wchar") ||
            (type == "int8") || (type == "uint8") ||
            (type == "int16") || (type == "uint16") ||
            (type == "int32") || (type == "uint32") ||
            (type == "int64") || (type == "uint64") ||
            (type == "float") || (type == "double"));
}

std::string GeneratorJava::CreatePackagePath(const std::string& domain, const std::string& package)
{
    std::string result = domain;
    CppCommon::StringUtils::ReplaceAll(result, ".", std::string(1, CppCommon::Path::separator()));
    return result + CppCommon::Path::separator() + package;
}

std::string GeneratorJava::ConvertEnumBase(const std::string& type)
{
    if (type == "byte")
        return "Byte";
    else if (type == "char")
        return "Byte";
    else if (type == "wchar")
        return "Integer";
    else if (type == "int8")
        return "Byte";
    else if (type == "uint8")
        return "Byte";
    else if (type == "int16")
        return "Short";
    else if (type == "uint16")
        return "Short";
    else if (type == "int32")
        return "Integer";
    else if (type == "uint32")
        return "Integer";
    else if (type == "int64")
        return "Long";
    else if (type == "uint64")
        return "Long";

    yyerror("Unsupported enum base type - " + type);
    return "";
}

std::string GeneratorJava::ConvertEnumType(const std::string& type)
{
    if (type == "byte")
        return "byte";
    else if (type == "char")
        return "byte";
    else if (type == "wchar")
        return "int";
    else if (type == "int8")
        return "byte";
    else if (type == "uint8")
        return "byte";
    else if (type == "int16")
        return "short";
    else if (type == "uint16")
        return "short";
    else if (type == "int32")
        return "int";
    else if (type == "uint32")
        return "int";
    else if (type == "int64")
        return "long";
    else if (type == "uint64")
        return "long";

    yyerror("Unsupported enum base type - " + type);
    return "";
}

std::string GeneratorJava::ConvertEnumSize(const std::string& type)
{
    if (type == "byte")
        return "1";
    else if (type == "char")
        return "1";
    else if (type == "wchar")
        return "4";
    else if (type == "int8")
        return "1";
    else if (type == "uint8")
        return "1";
    else if (type == "int16")
        return "2";
    else if (type == "uint16")
        return "2";
    else if (type == "int32")
        return "4";
    else if (type == "uint32")
        return "4";
    else if (type == "int64")
        return "8";
    else if (type == "uint64")
        return "8";

    yyerror("Unsupported enum base type - " + type);
    return "";
}

std::string GeneratorJava::ConvertEnumGet(const std::string& type)
{
    if (type == "byte")
        return "getAsByte";
    else if (type == "char")
        return "getAsByte";
    else if (type == "wchar")
        return "getAsInt";
    else if (type == "int8")
        return "getAsByte";
    else if (type == "uint8")
        return "getAsByte";
    else if (type == "int16")
        return "getAsShort";
    else if (type == "uint16")
        return "getAsShort";
    else if (type == "int32")
        return "getAsInt";
    else if (type == "uint32")
        return "getAsInt";
    else if (type == "int64")
        return "getAsLong";
    else if (type == "uint64")
        return "getAsLong";

    yyerror("Unsupported enum base type - " + type);
    return "";
}

std::string GeneratorJava::ConvertEnumRead(const std::string& type)
{
    if (type == "byte")
        return "readByte";
    else if (type == "char")
        return "readInt8";
    else if (type == "wchar")
        return "readInt32";
    else if (type == "int8")
        return "readInt8";
    else if (type == "uint8")
        return "readInt8";
    else if (type == "int16")
        return "readInt16";
    else if (type == "uint16")
        return "readInt16";
    else if (type == "int32")
        return "readInt32";
    else if (type == "uint32")
        return "readInt32";
    else if (type == "int64")
        return "readInt64";
    else if (type == "uint64")
        return "readInt64";

    yyerror("Unsupported enum base type - " + type);
    return "";
}

std::string GeneratorJava::ConvertEnumConstant(const std::string& type, const std::string& value)
{
    std::string result = value;

    if (type.empty())
    {
        // Fill flags values
        std::vector<std::string> flags = CppCommon::StringUtils::Split(value, '|', true);

        // Generate flags combination
        if (!flags.empty())
        {
            result = "";
            bool first = true;
            for (const auto& it : flags)
            {
                result += (first ? "" : "|") + CppCommon::StringUtils::ToTrim(it) + ".getRaw()";
                first = false;
            }
        }
    }

    return ConvertEnumConstantPrefix(type) + result + ConvertEnumConstantSuffix(type);
}

std::string GeneratorJava::ConvertEnumConstantPrefix(const std::string& type)
{
    if (type == "byte")
        return "(byte)";
    else if (type == "char")
        return "(char)";
    else if (type == "wchar")
        return "(char)";
    else if (type == "int8")
        return "(byte)";
    else if (type == "uint8")
        return "(byte)";
    else if (type == "int16")
        return "(short)";
    else if (type == "uint16")
        return "(short)";
    else if (type == "int32")
        return "(int)";
    else if (type == "uint32")
        return "(int)";
    else if (type == "int64")
        return "(long)";
    else if (type == "uint64")
        return "(long)";

    return "";
}

std::string GeneratorJava::ConvertEnumConstantSuffix(const std::string& type)
{
    if ((type == "int64") || (type == "uint64"))
        return "L";

    return "";
}

std::string GeneratorJava::ConvertPrimitiveTypeName(const std::string& type)
{
    if (type == "bool")
        return "Boolean";
    else if (type == "byte")
        return "Byte";
    else if (type == "char")
        return "Character";
    else if (type == "wchar")
        return "Character";
    else if (type == "int8")
        return "Byte";
    else if (type == "uint8")
        return "Byte";
    else if (type == "int16")
        return "Short";
    else if (type == "uint16")
        return "Short";
    else if (type == "int32")
        return "Integer";
    else if (type == "uint32")
        return "Integer";
    else if (type == "int64")
        return "Long";
    else if (type == "uint64")
        return "Long";
    else if (type == "float")
        return "Float";
    else if (type == "double")
        return "Double";

    return "";
}

std::string GeneratorJava::ConvertTypeName(const std::string& domain, const std::string& package, const std::string& type, bool optional)
{
    if (optional)
    {
        if (type == "bool")
            return "Boolean";
        else if (type == "byte")
            return "Byte";
        else if (type == "char")
            return "Character";
        else if (type == "wchar")
            return "Character";
        else if (type == "int8")
            return "Byte";
        else if (type == "uint8")
            return "Byte";
        else if (type == "int16")
            return "Short";
        else if (type == "uint16")
            return "Short";
        else if (type == "int32")
            return "Integer";
        else if (type == "uint32")
            return "Integer";
        else if (type == "int64")
            return "Long";
        else if (type == "uint64")
            return "Long";
        else if (type == "float")
            return "Float";
        else if (type == "double")
            return "Double";
    }

    if (type == "bool")
        return "boolean";
    else if (type == "byte")
        return "byte";
    else if (type == "bytes")
        return "java.nio.ByteBuffer";
    else if (type == "char")
        return "char";
    else if (type == "wchar")
        return "char";
    else if (type == "int8")
        return "byte";
    else if (type == "uint8")
        return "byte";
    else if (type == "int16")
        return "short";
    else if (type == "uint16")
        return "short";
    else if (type == "int32")
        return "int";
    else if (type == "uint32")
        return "int";
    else if (type == "int64")
        return "long";
    else if (type == "uint64")
        return "long";
    else if (type == "float")
        return "float";
    else if (type == "double")
        return "double";
    else if (type == "decimal")
        return "java.math.BigDecimal";
    else if (type == "string")
        return "String";
    else if (type == "timestamp")
        return ((Version() < 8) ? "java.util.Date" : "java.time.Instant");
    else if (type == "uuid")
        return "java.util.UUID";

    std::string ns = "";
    std::string t = type;

    size_t pos = type.find_last_of('.');
    if (pos != std::string::npos)
    {
        ns.assign(type, 0, pos + 1);
        ns = domain + ns;
        t.assign(type, pos + 1, type.size() - pos);
    }
    else if (!package.empty())
        ns = domain + package + ".";

    return ns + t;
}

std::string GeneratorJava::ConvertTypeName(const std::string& domain, const std::string& package, const StructField& field)
{
    if (field.array)
        return ConvertTypeName(domain, package, *field.type, field.optional) + "[]";
    else if (field.vector)
        return "java.util.ArrayList<" + ConvertTypeName(domain, package, *field.type, true) + ">";
    else if (field.list)
        return "java.util.LinkedList<" + ConvertTypeName(domain, package, *field.type, true) + ">";
    else if (field.set)
        return "java.util.HashSet<" + ConvertTypeName(domain, package, *field.key, true) + ">";
    else if (field.map)
        return "java.util.TreeMap<" + ConvertTypeName(domain, package, *field.key, true) + ", " + ConvertTypeName(domain, package, *field.type, true) +">";
    else if (field.hash)
        return "java.util.HashMap<" + ConvertTypeName(domain, package, *field.key, true) + ", " + ConvertTypeName(domain, package, *field.type, true) +">";

    return ConvertTypeName(domain, package, *field.type, field.optional);
}

std::string GeneratorJava::ConvertBaseFieldName(const std::string& domain, const std::string& type, bool final)
{
    std::string modelType = final ? "Final" : "Field";

    std::string ns = "";
    std::string t = type;

    size_t pos = type.find_last_of('.');
    if (pos != std::string::npos)
    {
        ns.assign(type, 0, pos + 1);
        ns.append("fbe.");
        ns = domain + ns;
        t.assign(type, pos + 1, type.size() - pos);
    }

    return ns + modelType + "Model" + ConvertTypeFieldName(t);
}

std::string GeneratorJava::ConvertTypeFieldName(const std::string& type)
{
    if (type == "bool")
        return "Boolean";
    else if (type == "byte")
        return "Byte";
    else if (type == "bytes")
        return "Bytes";
    else if (type == "char")
        return "Char";
    else if (type == "wchar")
        return "WChar";
    else if (type == "int8")
        return "Int8";
    else if (type == "uint8")
        return "Int8";
    else if (type == "int16")
        return "Int16";
    else if (type == "uint16")
        return "Int16";
    else if (type == "int32")
        return "Int32";
    else if (type == "uint32")
        return "Int32";
    else if (type == "int64")
        return "Int64";
    else if (type == "uint64")
        return "Int64";
    else if (type == "float")
        return "Float";
    else if (type == "double")
        return "Double";
    else if (type == "decimal")
        return "Decimal";
    else if (type == "string")
        return "String";
    else if (type == "timestamp")
        return ((Version() < 8) ? "Date" : "Timestamp");
    else if (type == "uuid")
        return "UUID";

    std::string result = type;
    CppCommon::StringUtils::ReplaceAll(result, ".", "");
    return result;
}

std::string GeneratorJava::ConvertTypeFieldType(const std::string& domain, const std::string& type, bool optional)
{
    if (type == "bool")
        return "Boolean";
    else if (type == "byte")
        return "Byte";
    else if (type == "bytes")
        return "java.nio.ByteBuffer";
    else if (type == "char")
        return "Character";
    else if (type == "wchar")
        return "Character";
    else if (type == "int8")
        return "Byte";
    else if (type == "uint8")
        return "Byte";
    else if (type == "int16")
        return "Short";
    else if (type == "uint16")
        return "Short";
    else if (type == "int32")
        return "Integer";
    else if (type == "uint32")
        return "Integer";
    else if (type == "int64")
        return "Long";
    else if (type == "uint64")
        return "Long";
    else if (type == "float")
        return "Float";
    else if (type == "double")
        return "Double";
    else if (type == "decimal")
        return "java.math.BigDecimal";
    else if (type == "string")
        return "String";
    else if (type == "timestamp")
        return ((Version() < 8) ? "java.util.Date" : "java.time.Instant");
    else if (type == "uuid")
        return "java.util.UUID";

    std::string ns = "";
    std::string t = type;

    size_t pos = type.find_last_of('.');
    if (pos != std::string::npos)
    {
        ns.assign(type, 0, pos + 1);
        ns = domain + ns;
        t.assign(type, pos + 1, type.size() - pos);
    }

    return ns + t;
}

std::string GeneratorJava::ConvertTypeFieldDeclaration(const std::string& domain, const std::string& type, bool optional, bool final)
{
    std::string modelType = final ? "Final" : "Field";

    std::string ns = "";
    std::string opt = optional ? "Optional" : "";
    std::string t = type;

    size_t pos = type.find_last_of('.');
    if (pos != std::string::npos)
    {
        ns.assign(type, 0, pos + 1);
        ns.append("fbe.");
        ns = domain + ns;
        t.assign(type, pos + 1, type.size() - pos);
    }
    else
        ns = (IsKnownType(type) && !optional) ? (domain + "fbe.") : "";

    return ns + modelType + "Model" + opt + ConvertTypeFieldName(t);
}

std::string GeneratorJava::ConvertTypeFieldDeclaration(const std::string& domain, const StructField& field, bool final)
{
    std::string modelType = final ? "Final" : "Field";

    if (field.array)
        return modelType + "ModelArray" + std::string(field.optional ? "Optional" : "") + ConvertTypeFieldName(*field.type);
    else if (field.vector || field.list || field.set)
        return modelType + "ModelVector" + std::string(field.optional ? "Optional" : "") + ConvertTypeFieldName(*field.type);
    else if (field.map || field.hash)
        return modelType + "ModelMap" + ConvertTypeFieldName(*field.key) + std::string(field.optional ? "Optional" : "") + ConvertTypeFieldName(*field.type);
    else if (field.optional)
        return modelType + "ModelOptional" + ConvertTypeFieldName(*field.type);

    return ConvertTypeFieldDeclaration(domain, *field.type, field.optional, final);
}

std::string GeneratorJava::ConvertTypeFieldInitialization(const std::string& domain, const StructField& field, const std::string& offset, bool final)
{
    std::string modelType = final ? "Final" : "Field";

    if (field.array)
        return "new " + modelType + "ModelArray" + std::string(field.optional ? "Optional" : "") + ConvertTypeFieldName(*field.type) + "(buffer, " + offset + ", " + std::to_string(field.N) + ")";
    else if (field.vector || field.list || field.set)
        return "new " + modelType + "ModelVector" + std::string(field.optional ? "Optional" : "") + ConvertTypeFieldName(*field.type) + "(buffer, " + offset + ")";
    else if (field.map || field.hash)
        return "new " + modelType + "ModelMap" + ConvertTypeFieldName(*field.key) + std::string(field.optional ? "Optional" : "") + ConvertTypeFieldName(*field.type) + "(buffer, " + offset + ")";
    else if (field.optional)
        return "new " + modelType + "ModelOptional" + ConvertTypeFieldName(*field.type) + "(buffer, " + offset + ")";

    std::string ns = "";
    std::string t = *field.type;
    std::string type = *field.type;

    size_t pos = type.find_last_of('.');
    if (pos != std::string::npos)
    {
        ns.assign(type, 0, pos + 1);
        ns.append("fbe.");
        ns = domain + ns;
        t.assign(type, pos + 1, type.size() - pos);
    }
    else
        ns = IsKnownType(type) ? (domain + "fbe.") : "";

    return "new " + ns + modelType + "Model" + ConvertTypeFieldName(t) + "(buffer, " + offset + ")";
}

std::string GeneratorJava::ConvertConstant(const std::string& domain, const std::string& package, const std::string& type, const std::string& value, bool optional)
{
    if (value == "true")
        return "true";
    else if (value == "false")
        return "false";
    else if (value == "null")
        return "null";
    else if (value == "min")
    {
        if ((type == "byte") || (type == "uint8") || (type == "uint16") || (type == "uint32") || (type == "uint64"))
            return ConvertConstantPrefix(type) + "0" + ConvertConstantSuffix(type);
        else if (type == "int8")
            return ConvertConstantPrefix(type) + "-128" + ConvertConstantSuffix(type);
        else if (type == "int16")
            return ConvertConstantPrefix(type) + "-32768" + ConvertConstantSuffix(type);
        else if (type == "int32")
            return ConvertConstantPrefix(type) + "-2147483648" + ConvertConstantSuffix(type);
        else if (type == "int64")
            return ConvertConstantPrefix(type) + "-9223372036854775808" + ConvertConstantSuffix(type);

        yyerror("Unsupported type " + type + " for 'min' constant");
        return "";
    }
    else if (value == "max")
    {
        if (type == "byte")
            return ConvertConstantPrefix(type) + "0xFF" + ConvertConstantSuffix(type);
        else if (type == "int8")
            return ConvertConstantPrefix(type) + "127" + ConvertConstantSuffix(type);
        else if (type == "uint8")
            return ConvertConstantPrefix(type) + "0xFF" + ConvertConstantSuffix(type);
        else if (type == "int16")
            return ConvertConstantPrefix(type) + "32767" + ConvertConstantSuffix(type);
        else if (type == "uint16")
            return ConvertConstantPrefix(type) + "0xFFFF" + ConvertConstantSuffix(type);
        else if (type == "int32")
            return ConvertConstantPrefix(type) + "2147483647" + ConvertConstantSuffix(type);
        else if (type == "uint32")
            return ConvertConstantPrefix(type) + "0xFFFFFFFF" + ConvertConstantSuffix(type);
        else if (type == "int64")
            return ConvertConstantPrefix(type) + "9223372036854775807" + ConvertConstantSuffix(type);
        else if (type == "uint64")
            return ConvertConstantPrefix(type) + "0xFFFFFFFFFFFFFFFF" + ConvertConstantSuffix(type);

        yyerror("Unsupported type " + type + " for 'max' constant");
        return "";
    }
    else if (value == "epoch")
        return ((Version() < 8) ? "new java.util.Date(0)" : "java.time.Instant.EPOCH");
    else if (value == "utc")
        return ((Version() < 8) ? "new java.util.Date(System.currentTimeMillis())" : "java.time.Instant.now()");
    else if (value == "uuid0")
        return domain + "fbe.UUIDGenerator.nil()";
    else if (value == "uuid1")
        return domain + "fbe.UUIDGenerator.sequential()";
    else if (value == "uuid4")
        return domain + "fbe.UUIDGenerator.random()";

    std::string result = value;

    if (!IsKnownType(type))
    {
        // Fill flags values
        std::vector<std::string> flags = CppCommon::StringUtils::Split(value, '|', true);

        // Generate flags combination
        if (flags.size() > 1)
        {
            result = "";
            bool first = true;
            for (const auto& it : flags)
            {
                std::string flag = CppCommon::StringUtils::ToTrim(it);
                size_t pos = flag.find_last_of('.');
                if (pos != std::string::npos)
                {
                    if (CppCommon::StringUtils::CountAll(flag, ".") > 1)
                        flag = domain + flag;
                    else
                        flag = (package.empty() ? "" : (domain + package + ".")) + flag;
                }
                result += (first ? "" : ", ") + flag + ".getEnum()";
                first = false;
            }
            result = (package.empty() ? "" : (domain + package + ".")) + type + ".fromSet(java.util.EnumSet.of(" + result + "))";
        }
        else
        {
            size_t pos = result.find_last_of('.');
            if (pos != std::string::npos)
            {
                if (CppCommon::StringUtils::CountAll(result, ".") > 1)
                    result = domain + result;
                else
                    result = (package.empty() ? "" : (domain + package + ".")) + result;
            }
        }
    }

    return ConvertConstantPrefix(type) + result + ConvertConstantSuffix(type);
}

std::string GeneratorJava::ConvertConstantPrefix(const std::string& type)
{
    if (type == "boolean")
        return "(boolean)";
    else if (type == "byte")
        return "(byte)";
    else if (type == "char")
        return "(char)";
    else if (type == "wchar")
        return "(char)";
    else if (type == "int8")
        return "(byte)";
    else if (type == "uint8")
        return "(byte)";
    else if (type == "int16")
        return "(short)";
    else if (type == "uint16")
        return "(short)";
    else if (type == "int32")
        return "(int)";
    else if (type == "uint32")
        return "(int)";
    else if (type == "int64")
        return "(long)";
    else if (type == "uint64")
        return "(long)";
    else if (type == "float")
        return "(float)";
    else if (type == "double")
        return "(double)";
    else if (type == "decimal")
        return "java.math.BigDecimal.valueOf(";
    else if (type == "uuid")
        return "java.util.UUID.fromString(";

    return "";
}

std::string GeneratorJava::ConvertConstantSuffix(const std::string& type)
{
    if ((type == "int64") || (type == "uint64"))
        return "L";
    else if (type == "float")
        return "f";
    else if (type == "double")
        return "d";
    else if (type == "decimal")
        return "d)";
    else if (type == "uuid")
        return ")";

    return "";
}

std::string GeneratorJava::ConvertDefault(const std::string& domain, const std::string& package, const std::string& type)
{
    if (type == "bool")
        return "false";
    else if (type == "byte")
        return "(byte)0";
    else if (type == "bytes")
        return "java.nio.ByteBuffer.allocate(0)";
    else if ((type == "char") || (type == "wchar"))
        return "'\\0'";
    else if ((type == "int8") || (type == "uint8"))
        return "(byte)0";
    else if ((type == "int16") || (type == "uint16"))
        return "(short)0";
    else if ((type == "int32") || (type == "uint32"))
        return "0";
    else if ((type == "int64") || (type == "uint64"))
        return "0L";
    else if (type == "float")
        return "0.0f";
    else if (type == "double")
        return "0.0d";
    else if (type == "decimal")
        return "java.math.BigDecimal.valueOf(0L)";
    else if (type == "string")
        return "\"\"";
    else if (type == "timestamp")
        return ((Version() < 8) ? "new java.util.Date(0)" : "java.time.Instant.EPOCH");
    else if (type == "uuid")
        return domain + "fbe.UUIDGenerator.nil()";

    std::string ns = "";
    std::string t = type;

    size_t pos = type.find_last_of('.');
    if (pos != std::string::npos)
    {
        ns.assign(type, 0, pos + 1);
        ns = domain + ns;
        t.assign(type, pos + 1, type.size() - pos);
    }
    else if (!package.empty())
        ns = domain + package + ".";

    return "new " + ns + t + "()";
}

std::string GeneratorJava::ConvertDefault(const std::string& domain, const std::string& package, const StructField& field)
{
    if (field.value)
        return ConvertConstant(domain, package, *field.type, *field.value, field.optional);

    if (field.array)
        return "new " + ConvertTypeName(domain, package, *field.type, field.optional) + "[" + std::to_string(field.N) + "]";
    else if (field.vector || field.list || field.set || field.map || field.hash)
        return "new " + ConvertTypeName(domain, package, field) + "()";
    else if (field.optional)
        return "null";
    else if (!IsPackageType(*field.type))
        return "new " + ConvertTypeName(domain, package, field) + "()";

    return ConvertDefault(domain, package, *field.type);
}

std::string GeneratorJava::ConvertOutputStreamType(const std::string& type, const std::string& name, bool optional)
{
    if (type == "bool")
        return ".append(" + name + " ? \"true\" : \"false\")";
    else if (type == "bytes")
        return ".append(\"bytes[\").append(" + name + ".array().length).append(\"]\")";
    else if ((type == "char") || (type == "wchar"))
        return ".append(\"'\").append(" + name + ").append(\"'\")";
    else if ((type == "string") || (type == "uuid"))
        return ".append(\"\\\"\").append(" + name + ").append(\"\\\"\")";
    else if (type == "timestamp")
        return ((Version() < 8) ? ".append(" + name + ".getTime() * 1000)" : ".append(" + name + ".getEpochSecond() * 1000000000 + " + name + ".getNano())");
    else
        return ".append(" + name + ")";
}

std::string GeneratorJava::ConvertOutputStreamValue(const std::string& type, const std::string& name, bool optional, bool separate)
{
    std::string comma = separate ? ".append(first ? \"\" : \",\")" : "";

    if (optional || (type == "bytes") || (type == "decimal") || (type == "string") || (type == "timestamp") || (type == "uuid"))
        return "if (" + name + " != null) sb" + comma + ConvertOutputStreamType(type, name, true) + "; else sb" + comma + ".append(\"null\");";
    else
        return "sb" + comma + ConvertOutputStreamType(type, name, false) + ";";
}

} // namespace FBE
